Ignorando as restrições da API para diversão e lucro

Feb 08 2023
Recentemente, baixei e comecei a testar um aplicativo localmente que fornecia acesso ao painel junto com pontos de extremidade da API de descanso para que seus usuários interagissem facilmente com sua funcionalidade. No entanto, ao brincar com suas APIs usando a documentação disponível, percebi que algumas das APIs não estavam acessíveis.
Créditos da imagem: https://blog.open-xchange.com/ox-bug-bounty-programs-two-years-in/

Recentemente, baixei e comecei a testar um aplicativo localmente que fornecia acesso ao painel junto com pontos de extremidade da API de descanso para que seus usuários interagissem facilmente com sua funcionalidade. No entanto, ao brincar com suas APIs usando a documentação disponível, percebi que algumas das APIs não estavam acessíveis. De acordo com a mensagem de erro, percebi que para usar as APIs indisponíveis, teria que comprar a versão premium do aplicativo. Neste blog, falo como consegui contornar as restrições. Para simplificar, vamos marcar todos os nossos componentes de aplicativos usados ​​no blog:

  • O aplicativo é redacted.com
  • A API disponível é GET /start/transaction
  • A API indisponível no nível gratuito (atualmente usada localmente, mas disponível na versão premium) é POST /stop/transaction

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

Comecei a fazer proxy de todo o tráfego do painel por meio do Burp Suite. Para minha surpresa, os endpoints exatos estavam sendo reutilizados no painel, o que confirmou a existência dos endpoints. Agora, a questão permanece: como o aplicativo está bloqueando minhas solicitações do Postman, mas parece funcionar no painel?

Comecei a analisar os cabeçalhos de solicitação. Abaixo está a solicitação enviada para o terminal de transação stop através do 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"
}

  • O campo X-Api-Token que tinha um token diferente da chave de API gerada.
  • O campo X-Cookie que provavelmente era o token da sessão.

Agora o objetivo estava claro, para usar o endpoint como uma API, eu precisaria criar uma solicitação que:

  • Tenha o valor do cabeçalho X-Api-Token.
  • Tenha o token de sessão calculado e anexado.
  • Tenha o comprimento de conteúdo correto calculado e anexado.

Para o token de sessão, analisei o fluxo de trabalho de login por meio do Burp. A solicitação de login foi algo como isto:

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"}

Meu script final que contornou as restrições da API é o seguinte:

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")

Embora o impacto não tenha sido alto para este aplicativo, você nunca sabe o que pode encontrar :p . Espero que este blog seja útil para caçadores de recompensas de insetos!