Bypassare le restrizioni API per divertimento e profitto

Feb 08 2023
Di recente, ho scaricato e iniziato a testare un'applicazione in locale che forniva l'accesso al dashboard insieme agli endpoint API rimanenti affinché i suoi utenti potessero interagire facilmente con le sue funzionalità. Tuttavia, mentre giocavo con le sue API utilizzando la documentazione disponibile, mi sono reso conto che alcune delle API non erano accessibili.
Crediti immagine: https://blog.open-xchange.com/ox-bug-bounty-programs-two-years-in/

Di recente, ho scaricato e iniziato a testare un'applicazione in locale che forniva l'accesso al dashboard insieme agli endpoint API rimanenti affinché i suoi utenti potessero interagire facilmente con le sue funzionalità. Tuttavia, mentre giocavo con le sue API utilizzando la documentazione disponibile, mi sono reso conto che alcune delle API non erano accessibili. Come da messaggio di errore, mi sono reso conto che per utilizzare le API non disponibili avrei dovuto acquistare la versione premium dell'app. In questo blog, parlo di come sono stato in grado di aggirare le restrizioni. Per semplicità, tagghiamo tutti i componenti della nostra app utilizzati nel blog:

  • L'app è redacted.com
  • L'API disponibile è GET /start/transaction
  • L'API non disponibile nel livello gratuito (attualmente utilizzata localmente ma disponibile nella versione premium) è POST /stop/transaction

{ 
"API is accessible only from dashboard. Please upgrade to premium for programmatic access."
}

Ho iniziato a delegare tutto il traffico della dashboard tramite Burp Suite. Con mia grande sorpresa, gli endpoint esatti venivano riutilizzati nella dashboard, il che confermava l'esistenza degli endpoint. Ora la domanda rimane, in che modo l'applicazione blocca le mie richieste di Postman ma sembra funzionare dalla dashboard?

Ho iniziato ad analizzare le intestazioni della richiesta. Di seguito è riportata la richiesta inviata all'endpoint della transazione di arresto tramite Burp:

POST /stop/transaction
Host: redacted.com
Content-Length: 3144
Sec-Ch-Ua: “Chromium”;v=”109", “Not_A Brand”;v=”99"
Sec-Ch-Ua-Platform: “macOS”
X-Api-Token: "$api_token"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.120 Safari/537.36
X-Cookie: token="$token"
Content-Type: application/json
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://redacted.com/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close

{
"Json data"
}

  • Il campo X-Api-Token che aveva un token diverso dalla chiave API generata.
  • Il campo X-Cookie che probabilmente era il token di sessione.

Ora l'obiettivo era chiaro, per utilizzare l'endpoint come API, avrei dovuto creare una richiesta che avrebbe:

  • Avere il valore dell'intestazione X-Api-Token.
  • Calcolare e aggiungere il token di sessione.
  • Avere la lunghezza del contenuto corretta calcolata e aggiunta.

Per il token di sessione, ho analizzato il flusso di lavoro di accesso tramite Burp. La richiesta di accesso era qualcosa del genere:

POST /session HTTP/1.1
Host: redacted.com
Content-Length: 55
Sec-Ch-Ua: "Chromium";v="109", "Not_A Brand";v="99"
Sec-Ch-Ua-Platform: "macOS"
X-Api-Token: "$api_token"
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.120 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: https://redacted.com
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://redacted.com/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Connection: close

{"username":"admin","password":"admin"}

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
X-Frame-Options: DENY
Content-Type: application/json
Connection: close
Content-Length: 179
Expires: 0
Expect-CT: max-age=0
Pragma: no-cache

{"token":"$token"}

Il mio script finale che ha aggirato le restrizioni dell'API è il seguente:

import requests
import json

#Credentials needed to automate session
username= "admin"
password= "admin"
x_api_token="1AB-345-678"

#Function to generate headers for Login
def headers_data(request_data,if_login,token=0):
    data_len= str(len(request_data))
    headers_request = {
    'Content-Length': data_len,
    'Content-Type': 'application/json',
    'X-API-Token': x_api_token
    }    
    if if_login:
        return headers_request
    else:
        headers_request['X-Cookie']= 'token='+token
    return headers_request 

#Login to application and store token
login_data= '{"username":' + '"' + username + '"'+',"password":' + '"' + password + '"'+'}'
login_headers = headers_data(login_data,True)
try:
    login_tokens = requests.post('https://redacted.com/session', headers=login_headers, data=login_data, verify=False)
    token= login_tokens.json().get('token')
    print("Login Successful !")
except:
    raise Exception("Login Automation failed") 

#Post request to stop endpoint.
payload_data= { "Some json data to be posted to stop transaction"}
update_ip_headers = headers_data(payload_data,False,token)
stop_transaction_response= requests.put('https://redacted.com/stop/transaction', headers=update_ip_headers, data=payload_data, verify=False)
print("Transaction stopped via API")

Sebbene l'impatto non sia stato elevato per questa applicazione, non sai mai cosa potresti incontrare :p . Spero che questo blog sia utile per i cacciatori di bug taglie!