Today, Brian Krebs reported on account takeovers happening at Experian, one of the 3 major credit agencies. The first step after getting account access is to lock out the account owner, usually by swapping the email address. 10 years ago I dealt with this problem extensively, so I'd like to share how to solve it.
// Share.on([hacker news,
linkedin,
twitter,
facebook,
reddit])
Discuss on X (Twitter).
Back in 2012 I took the lead on fixing a balked conversion of a 14 year old high end marketplace. I expected to work out the rough edges and do some performance tuning of the site and database. Instead, I accidentally ended up spending over a year combating phishing and fraud targeted against about 300,000 users. We dealt with a lot of different attack vectors, which I've discussed at conferences like M3AAWG before but probably need to get around to writing about...so we'll start with the account takeover problem and how to fix it.
An account takeover happens when somebody manages to log into a user's account without authorization and then proceeds to lock the original account owner out so they can't get it back. This usually happens when somebody falls for a phishing email impersonating the login page. Sometimes it's by socially engineering support channels. This recent Experian incident involved simply signing up for the account again with a new email address, which is pretty scary.
When account takeovers happen, victims immediately reach out to have the problem corrected. They'll end up talking to support who then has to verify everything about the person, verify what happened in the system, verify that the person contacting support isn't trying to engineer them to takeover the account and then get it resolved...until it happens again. No matter what's happening in the support queue, fraud has to jump to the top of the priority list to minimize the damage which compounds all of the other support responsibilities. With a small support team, as we had back in 2012, it gets to be a heavy burden before even talking about the feeling of violation and fear in your site users that impacts their confidence in the service as a whole.
Luckily, this is one of the easier issues to correct with a 2 part, self-service approach.
First and foremost, if the primary means of communicating with your users is via email you need to make sure that email is a trusted and reliable channel. DMARC helps with that by ensuring that emails which say they come from your domain, actually come from your domain. It builds on top of 2 protocols, SPF and DKIM, by publishing a DNS record which tells mail servers that only email which passes either SPF or DKIM authentication should be allowed to make it to the customer inbox. Both are necessary because SPF doesn't survive forwarding, which is out of your control. DKIM does survive forwarding, but if it's not present mail servers don't have any way to know it's supposed to be there.
DMARC provides slow ramp up options where you can only apply the rule to a small percentage of delivered mail, have messages that fail authentication go to quarantine or have them not be delivered at all. You'll also have reports emailed to you from different providers like Google letting you know how they handled mail that said it was from your domain. If people are going after your users, step one is going to be to send phishing emails that appear to be from you, sending your users to a fake login page. If you allow them to directly imitate your domain, you're making it easy for them.
DMARC is easier to implement when you have fewer outbound email servers. If you're using a service like Sendgrid rather than having individual servers sending out email, it's a lot easier. If you're a large company, DMARC roll out is probably going to be a 3-6 month project that a company like dmarcian can help guide you through. Disclosure, I worked for dmarcian for 3 years after seeing how effective DMARC was in practice.
ANNOUNCEMENT: At BSides on October 28th, 2023 I announced the upcoming 2024 private beta of dmarcSTAR, a service that approaches DMARC in what I believe is the ideal manner after over a decade of experience with the specification. Read more at dmarcSTAR.com.
If a user's account is accessed by an unauthorized person, the first thing they're going to want to do is lock out the original owner. This will mean changing the account password and then ensuring that the original owner can't simply issue a password reset by also changing the associated email address. Most popular login systems include a field for last_email_address
on the user account, but this isn't enough. It's well known enough that, once an account is compromised the email address is changed 2-3 different times to wipe the field.
First, create a table called user_email_changes
or something along those lines with at minimum, the following fields.
user_id
- Foreign key of the associated user accountemail_from
- The email address associated with the account before this change was requestedemail_to
- The email address that's been requested to transition toconfirm_key
- A unique hash that will be used to verify the new email addressreversal_key
- A unique hash that will be used to allow the original owner to reverse this change if they didn't request itcreated_at
- Timestamp of when this request was madecreated_ip
- IP address of the user who requested the changeconfirmed_at
- Timestamp of when the new email address was successfully confirmedconfirmed_ip
- IP address of the user who verified the new email addressreversed_at
- Timestamp of when the original account owner requested the change be revertedreversed_ip
- IP address of the user who requested the change reversalNow, whenever an email address change is requested on an account we're going to create a new entry in this table. Track the user, the current email, the new email, generate a couple of unique hashes for the confirm and reversal keys while we record the timestamp and the IP address.
Next up, we're going to send an email to the new address with a link including the confirm_key
. We don't actually change the email address on the account until after it has been confirmed with this link. Otherwise there's nothing stopping an attacker from just making something up to further obscure their own identity. If we're not dealing with an attacker and it's just normal user behavior, then the verification step just helps us to make sure that the new email address wasn't typed incorrectly.
One the confirmation link has been clicked, we'll have our system clear out the hash from the table, record the confirmed_at
timestamp along with the IP address of the person who clicked the link and finally change the email address on the account.
We aren't going to bother sending an email to the original email address, tracked in the email_from
field, until the new email has been confirmed successfully. There's no reason to bother a user with false positives because eventually they'll stop taking them seriously. In this message, we will notify the old email address of the change with some additional information to help them verify it. Provide the new email address that it was changed to, the IP address that made the change and if possible include some simple geographic information about the IP address like the state or country of origin for context.
In this email, we'll want to include a blurb that if they did not request this change then their account may have been compromised and to please "click here" to revert it back to the original address, by using a link which includes our reversal key. Even if users never click this link, seeing the email provides additional assurance that their account is protected against something like that in the future.
Here's where this structure matters. If whoever took over the account has changed the email address 20 times, the original user will only have that one email with the original reversal link. We have to ensure that link will always work to correct the change and that none of the reversal links created afterwards will work if they use it. Otherwise they could try to revert the account and when the intruder realizes it, they could just invoke one of their many reversal links from the next 20 email changes to retake the account.
When the user clicks the reversal link, we'll need to take the following steps. Ideally, all of these should be done in a single database transaction if possible but system designs will vary.
email_from
that was associated with the referral linkuser_email_changes
records associated with that user_id
which were created after the one we're currently using to set the confirm_key
and reversal_key
fields to NULL
reversed_at
timestamp and track the IP addressNext we're going to send the user to a password reset screen and prompt them to enter a new password, while providing a security warning. The user should be informed that if they did not make these account changes then it is possible that an unauthorized user had accessed their account and they should review their account activity. Additionally, if some form of multi-factor authentication is available on the site they should be encouraged to set it up to prevent issues like this in the future.
Tracking the IPs during change is helpful during an investigation into repeat offenders or issues at scale, but obviously IPs are not a clear indicator of a specific individual. Some type of additional tracking to try to identify the specific user or machine involved at each step of this process goes a long way if you have it.
Creepy tracking techniques like Evercookie are uncomfortable, but very beneficial for security and fraud prevention where the perpetrators are always trying to hide their identity. If you can associate an identifier with multiple accounts, future cleanup and prevention gets tremendously easier.
For a user who's account is compromised, all they will ever see is a message that the email address was changed and a link to reverse it, followed by a password change form with a warning. The technique applies a tremendous amount of protection to users, reduces your support burden, makes users feel safer all while being non-intrusive.
Ideally, this should be built into every packaged authentication system that uses email addresses. If you have one and you'd like me to give it a code review or a test run, I will be happy to. Just shoot me a note on LinkedIn or Twitter.
// Share.on([hacker news,
linkedin,
twitter,
facebook,
reddit])