They really should be hashed :)
Related incident: https://wikitech.wikimedia.org/wiki/Incident_documentation/20161112-OurMine
| Reedy | |
| Sep 16 2016, 11:34 PM |
| F34961563: 0001-WIP-Encrypt-OTP-secret-in-the-database.patch | |
| Feb 22 2022, 5:59 AM |
They really should be hashed :)
Related incident: https://wikitech.wikimedia.org/wiki/Incident_documentation/20161112-OurMine
My 2 cents:
Any wiki that uses CentralAuth has it stored in one big table on s7's centralauth db, so logging into any other clustered wiki mean they get the same 2FA protection, and all is hunky dory
Private, fishbowl etc which aren't CentralAuth'd are in their local db.
There is certainly security improvements storing it on other server(s), but the usual reason for doing that so far has been to store things like AFT, Echo, Flow etc in extension1 where they have potentially a lot of data
Although some sites (such as Google, Twitter) let you view them, we've vaguely decided on another ticket that this isn't probably something we want to do, and as such, need a nicer way to regenerate them
The user may not have a password at all. With AuthManager, a user could use a non-password-based PrimaryAuthenticationProvider (if one is available on the wiki) along with OATHAuth.
I believe I said this in another bug, but it would be better to generate new scratch tokens rather than allowing users to see old ones again. IMO it's a small defense against misuse of scratch tokens, and avoids transmitting the same secret to the user more than once. (And, of course, it has the added benefit of allowing us to hash them!)
@Xaosflux noticed this vulnerability in a discussion on enwiki, brought it up to me privately, and asked to be included in the discussion when I mentioned we already had a bug about it. Any objections?
My ISP are having great fun with these sorts of things atm... http://www.revk.uk/2016/12/the-totp-seed-storage-dilemma.html
I've been meaning to file a bug about that for a while: T183420: Authentication data should not be available through the normal DB abstraction layer
Any objection to making this task public? It is a bit of a stretch of calling this a security vulnerability - it is basically the OATHAuth equivalent of T150647: Deploy EncryptedPassword to Wikimedia Sites which is also not private. (The similar T119451: Consider using "pepper" for our hashed passwords is also not secret, OTOH {T50698} is, so it looks like we don't have consistent standards in this regard...)
Yes. Those can be hashed though, I believe once shown to the user we don't need the real value anymore.
@Reedy, @sbassett, can we make this public? I agree with @Tgr that this is not really a security vulnerability, more of a hardening feature in case of a DB leak or SQLI.
My proposal to fix this is:
I'm intentionally leaving alone scratch tokens because we want to migrate those to something else entirely (T232336: Separate recovery codes into a separate 2FA module), and can figure out a hashing scheme then.
Ignore this question, we have to use a random nonce, because private wikis will have overlapping user ids, and the docs say "The nonce doesn't have to be confidential, but it should never ever be reused with the same key". Anyways, sticking the random nonce in the JSON blob is also trivial. We don't even need the encrypted => flag then, we can just look isset( $data['nonce'] ).
Here's what I've got so far, it works in my limited testing. I would appreciate some feedback or a thumbs up that I'm going in the right direction before I keep working on this.
In the case the secret key is ever leaked and needs rotating, I think it would also be a good idea as a follow-up to have a script that can decrypt using the old, leaked key and then re-encrypt using the new secret key.
Two questions that come to mind:
(Nit: the unset() call looks pointless.)
I briefly looked into $wgSecretKey earlier, and yeah, it has no guarantees on format, though I think we can expect most installs to use the generated default. The other thing that concerns me is that currently docs state it's safe to just reset whenever, which would not be true if we started using it for encryption. Maaaybe core should have a $wgEncryptionKey or something with stricter requirements on format and resetability, but I don't really want to get into that now.
- Is this worth making libsodium a hard requirement? It's a low bar for a crypto-related extension, but it would be pretty trivial to skip encryption/decryption when there's no sodium support.
I am leaning to making it optional too, especially since it's bundled and core doesn't require libsodium (yet). This would also mean that we don't need to fallback to $wgSecretKey, and only enable encryption support when libsodium and $wgOATHSecret are set. I think for most installs just having 2FA would be a general increase in security even if the 2FA part is vulnerable to the database being leaked.
I should have another patch for review later this week, thanks for the feedback!
Since there's no exploitable/exploited vuln here (unless someone secretly owns our dbs or we're unknowingly leaking it somewhere - no evidence to support those notions right now), we can make this public.
Change 766557 had a related patch set uploaded (by Legoktm; author: Legoktm):
[mediawiki/extensions/OATHAuth@master] [WIP] Optionally encrypt OTP secret in the database
oh no :( several times when I lost access, I just looked up the secret in the database.
Jokes aside, thank you for working on this Kunal!
@Legoktm: Removing task assignee as this open task has been assigned for more than two years - see the email sent to all task assignees on 2024-04-15.
Please assign this task to yourself again if you still realistically [plan to] work on this task - it would be welcome! :)
If this task has been resolved in the meantime, or should not be worked on by anybody ("declined"), please update its task status via "Add Action… 🡒 Change Status".
Also see https://www.mediawiki.org/wiki/Bug_management/Assignee_cleanup for tips how to best manage your individual work in Phabricator. Thanks!
I've updated the patch a bit: https://gerrit.wikimedia.org/r/c/mediawiki/extensions/OATHAuth/+/766557. Would appreciate some CR as this is needed to advance T232336.
Change #766557 merged by jenkins-bot:
[mediawiki/extensions/OATHAuth@master] Optionally encrypt OTP secret in the database
Note: the above patch should be a noop until $wgOATHSecretKey is set to a non-falsey value in PS.php in Wikimedia production.
Change #1187101 had a related patch set uploaded (by SBassett; author: Legoktm):
[mediawiki/extensions/OATHAuth@wmf/1.45.0-wmf.18] Optionally encrypt OTP secret in the database
Change #1187101 merged by jenkins-bot:
[mediawiki/extensions/OATHAuth@wmf/1.45.0-wmf.18] Optionally encrypt OTP secret in the database
Mentioned in SAL (#wikimedia-operations) [2025-09-11T20:07:34Z] <sbassett@deploy1003> Started scap sync-world: Backport for [[gerrit:1187101|Optionally encrypt OTP secret in the database (T145915)]]
Mentioned in SAL (#wikimedia-operations) [2025-09-11T20:13:24Z] <sbassett@deploy1003> sbassett: Backport for [[gerrit:1187101|Optionally encrypt OTP secret in the database (T145915)]] synced to the testservers (see https://wikitech.wikimedia.org/wiki/Mwdebug). Changes can now be verified there.
Mentioned in SAL (#wikimedia-operations) [2025-09-11T20:20:13Z] <sbassett@deploy1003> Finished scap sync-world: Backport for [[gerrit:1187101|Optionally encrypt OTP secret in the database (T145915)]] (duration: 12m 38s)