The target was a company with over 50 worldwide brands, with a lot of them using the company's OpenID system for authentication. The company's main website was in this case, the Identity Provider and each brand / website that relied on it, had to implement it and configure the OpenID flow. When testing a website that was recently added to the program's scope, I noticed that unlike others, this one was enforcing two factor authentication through Google Authenticator. Looking at the requests sent in the background, I noticed that when clicking the login button a request simillar to this one is sent:
The first thing that stood out was the acr_values
parameter. I haven't encountered it before when looking at OpenID flows, so I thought it was some custom configuration that would lead to an easy 2FA bypass. The first and obvious idea was to try removing the otp
value and only keeping the password
value. While I was correctly redirected to the Identity Provider's login page, upon logging in with correct credentials, I was always facing a 401 if the otp
value was removed.
After further testing, It became increasingly apparent that this was not a rushed 2FA implementation, but it was a well established protocol explained in RFC 8176.
Typically, each "amr" value provides an identifier for a family of closely related authentication methods. For example, the "otp" identifier intentionally covers OTPs (One-Time Passwords) based on both time and HMAC (Hashed Message Authentication Code). Many relying parties will be content to know that an OTP has been used in addition to a password; the distinction between which kind of OTP was used is not useful to them. Thus, there's a single identifier that can be satisfied in two or more nearly equivalent ways.
Basically, the acr_values
parameter would tell the Identity Provider what authentication methods the client requests. Upon fulfilling the login flow, the callback to the client website will contain a JWT, which if decoded, would contain the AMR value used like so:
{"alg":"HS256","typ":"JWT"}.{"state":"123456789","auth_time":1234,"amr":["pwd","otp"] ...
Tampering with the values before and after logging in with the identity provider were just welcomed by a bunch of 401 errors, so I gave up on that idea quite fast.
Section 5 of RFC 8176 states the following security considerations when implementing AMR:
taking a dependence upon particular authentication methods may result in brittle systems since the authentication methods that may be appropriate for a given authentication will vary over time.
Therefore, OpenID configurations relying on AMR should make sure to only accept trusted and validated authentication methods. Authentication methods that may be appropriate for a given authentication will vary over time, both because of the evolution of attacks on existing methods and the deployment of new authentication methods.
Reading the above had me thinking that there might be some other available acr values I could test. The second section of the rfc lists 22 defined authentication methods, so I decided to test a few. Shortly after, upon switching the acr_values
value from otp+password
to sms+password
and entering the credentials, I was greeted with the following image:
This was looking promising, so I used a one time SMS verification service and followed through the proccess. Upon adding the phone number and confirming ownership, I succesfully skipped the Google Authenticator window and was also logged in. I reported the issue and it was triaged and paid as High severity. The team let me know that this was caused because the client website had both OTP and SMS enabled, even though there was no UI for enabling sms as a two factor authentication method. This is a clear case on how easy it is to misconfigure the AMR protocol and introduce unwanted security vulnerabilities.