XUT_ / COURSES / PENETRATION TESTER

Modifying a Burp Extension

Después de haber obtenido la contraseña de ecorp_user de la tarea anterior, inicia sesión.

Modifying a Burp Extension

La aplicación usa una forma ligeramente diferente de encriptar la petición y la respuesta, como se muestra en el js /static/form-verify-submit.js.


Cómo Encripta los Datos la Aplicación Web

Antes de modificar la extensión de Burp, vamos a entender cómo el código JS encripta las peticiones de autentificación. Los componentes clave son:

  1. ROT13 Encoding

Antes de encriptar, el username y secret se ofuscan usando ROT13.

js
function rot13(message) {
    const originalAlpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const cipher = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM";
    return message.replace(/[a-z]/gi, letter => cipher[originalAlpha.indexOf(letter)]);
}
  • ROT13 cambia cada carácter 13 lugares en adelante, dando la vuelta si es necesario.
  • Ejemplo:
    • ecorp_user --> rpebc_hfre
    • 0007 (no tiene efecto porque ROT13 sólo afecta a letras)
  1. AES Key Generation

Se genera una clave AES aleatoria para cada petición:

js
const rawAesKey = window.crypto.getRandomValues(new Uint8Array(16));
const aesKey = await getSecretKey(rawAesKey);
  • getSecretKey() importa la clave aleatoria en modo AES-CBC.
  1. Encriptar las Credenciales
js
let rawdata = "username=" + rot13(formDataObj["username"]) + "&secret=" + rot13(formDataObj["secret"]);
let data = window.btoa(String.fromCharCode(...new Uint8Array(await encryptMessage(aesKey, enc.encode(rawdata).buffer))));
  • Las credenciales ROT13-encoded son:
    • "username=rpebc_hfre&secret=0007"
  • Esta string es encriptada con AES usando la clave random y el valor IV fijado: "0000000000000000".
  • Los bytes encriptados son base64-encodeados antes de ser mandados.
  1. Subir la Petición
js
body: "mac=" + encodeURIComponent(window.btoa(String.fromCharCode(...rawAesKey))) +
       "&data=" + encodeURIComponent(data)
  • mac= contiene la clave AES base64-encodeada.
  • data= contiene las credenciales encriptadas encodeadas en base64.

Cómo Desencripta la Información Nuestra Extensión de Burp

Ahora que sabemos cómo la encriptación del lado del cliente funciona, vamos a examinar la extensión de Burp 102Burp, que intercepta, desencripta y analiza peticiones.

  1. Inicializar la Extensión de Burp
java
public class BurpExtender implements BurpExtension, HttpHandler {
    private MontoyaApi api;
    private static final String FIXED_IV = "0000000000000000"; // 16-byte IV
    private Logging logging;

    @Override
    public void initialize(MontoyaApi api) {
        this.api = api;
        this.logging = api.logging();
        api.extension().setName("Burp Decryptor");

        // Register the HTTP request handler
        api.http().registerHttpHandler(this);
        logging.logToOutput("Extension Loaded Successfully");
    }
}

Para analizar y desencriptar la petición y respuesta de la aplicación, la extensión de Burp debe interceptar la peticiones HTTP y extraer los valores necesarios antes de que lleguen al servidor. La extensión de Burp comienza la ejecución mediante el método initialize().

Esta función configura el nombre de la extensión a "Burp Decryptor" y registra un handler HTTP. Esto permite que la extensión modifique peticiones y respuestas mientras pasan por Burp.

  1. Interceptar las Peticiones de Autentificación
java
@Override
public RequestToBeSentAction handleHttpRequestToBeSent(HttpRequestToBeSent requestToBeSent) {
    String requestBody = requestToBeSent.body().toString();

    // Extract MAC (AES key) and data (encrypted payload)
    Pattern macPattern = Pattern.compile("mac=([^&]+)");
    Pattern dataPattern = Pattern.compile("data=([^&]+)");

    Matcher macMatcher = macPattern.matcher(requestBody);
    Matcher dataMatcher = dataPattern.matcher(requestBody);

Una vez cargada la extensión, monitoriza las peticiones HTTP salientes. Cada petición interceptada es pasada al handleHttpRequestToBeSent(), donde es analizada en busca de la presencia de mac y de parámetros de datos. Estos parámetros contienen la clave AES y credenciales encriptadas.

Aquí, el cuerpo de la petición de recupera como string y se comprueba usando expresiones regulares para localizar la mac y los parámetros. Si esos parámetros son encontrados, son decodeados de base64.

java
if (macMatcher.find() && dataMatcher.find()) {
    try {
        byte[] aesKey = Base64.getDecoder().decode(URLDecoder.decode(macMatcher.group(1), StandardCharsets.UTF_8));
        byte[] encryptedData = Base64.getDecoder().decode(URLDecoder.decode(dataMatcher.group(1), StandardCharsets.UTF_8));

La clave AES extraída de la mac está ahora en formato de byte, lista para usarse para la desencriptación. Las credenciales encriptadas, extraídas de información, son también decodeadas en base64 hacia el formato original de array de bytes.

  1. Desencriptar las Credenciales
java
// Decrypt AES-CBC encrypted data
        byte[] decryptedBytes = decryptAES(encryptedData, aesKey);
        String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8).trim();

Una vez que la clave AES y la información encriptada son extraídas, la extensión usa la función decryptAES() para invertir la encriptación. Esta usa AES-CBC con pading PKCS5 y el mismo IV que fue usado en el JS.

Se implementa de la siguiente manera:

java
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));
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");

    cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
    return cipher.doFinal(encryptedData);
}

Usando la clave AES obtenida de la petición, esta función invierte exitosamente la encriptación aplicada en el lado del cliente, revelando las credenciales originales en formato encodeado ROT13.

  1. Invertir el ROT13 Encoding

Como el JavaScript original ofuscaba las credenciales usando ROT13 antes de la encriptación, debemos aplicar ROT13 de nuevo para restaurar los valores originales.

java
  // Apply ROT13 decoding
        String firstPassRot13 = rot13(decryptedData);
        String finalDecryptedData = doubleDecodeParameterNames(firstPassRot13);

La función rot13() gira cada letra 13 lugares en el alfabeto, convirtiendo rpebc_hfre en ecorp_user y frperg en secret.

java
private String rot13(String text) {
    StringBuilder result = new StringBuilder();
    for (char c : text.toCharArray()) {
        if (Character.isLetter(c)) {
            if (Character.isUpperCase(c)) {
                result.append((char) ('A' + (c - 'A' + 13) % 26));
            } else {
                result.append((char) ('a' + (c - 'a' + 13) % 26));
            }
        } else {
            result.append(c);
        }
    }
    return result.toString();
}
  1. Registrar la Información Desencriptada

Con las credenciales desencriptadas e invertidas exitosamente, son registradas en el log de eventos de Burp Suite.

java
// Log fully decoded request
        logging.logToOutput("\n===== [Decrypted Request] =====");
        logging.logToOutput(finalDecryptedData);
        logging.logToOutput("========================================\n");
  1. Manejar las Respuestas del Servidor

Mientras la extensión de Burp intercepta y desencripta las peticiones de autentificación, también debe procesar y desencriptar las respuestas del servidor. Cuando la aplicación web recibe una petición de autentificación, procesa las credenciales otorgadas y devuelve la respuesta encriptada.

La respuesta del servidor sigue un patrón de encriptación similar al de la petición. En lugar de transmitir el resultado de la autentificación en texto plano, el servidor encripta la respuesta usando AES-CBC y el mismo IV y manda el parámetro result=. Como la clave AES no se incluye en la respuesta, la extensión de Burp debe recuperarla de la petición original.

Para manejar esto, la extensión usa el método handleHttpResponseReceived(), que es disparado cuando una petición HTTP para por Burp Suite:

java
@Override
public ResponseReceivedAction handleHttpResponseReceived(HttpResponseReceived responseReceived) {
    String responseBody = responseReceived.body().toString();

En este punto, la variable responseBody mantiene la respuesta encriptada del servidor como string. El siguiente paso es extraer el parámetro encriptado result= usando una expresión regular:

java
Pattern resultPattern = Pattern.compile("result=([^&]+)");
Matcher resultMatcher = resultPattern.matcher(responseBody);

Este patrón busca el parámetro result= y captura el valor base64-encodeado.

Sin embargo, antes de que la desencriptación tenga lugar, la extensión necesita recuperar la clave AES de la petición. Esto se hace accediendo al parámetro mac= de la petición:

java
    if (resultMatcher.find()) {
        try {
            // Retrieve the AES key from the original request
            String requestBody = responseReceived.initiatingRequest().body().toString();
            Pattern macPattern = Pattern.compile("mac=([^&]+)");
            Matcher macMatcher = macPattern.matcher(requestBody);

            if (!macMatcher.find()) {
                logging.logToError("Could not retrieve AES key from request.");
                return ResponseReceivedAction.continueWith(responseReceived);
            }

            byte[] aesKey = Base64.getDecoder().decode(URLDecoder.decode(macMatcher.group(1), StandardCharsets.UTF_8));

La extensión extrae la clave AES encodeada en base64 de la petición original, la decodea, y marca un array de bytes. Si no se encuentra parámetro mac= la extensión registra un error y permite que la repuesta pase sin cambio.

Una vez que la clave AES es recuperada, la extensión desencripta el parámetro result=. Primero, la respuesta encriptada es extraída y decodeada de base64.

java
byte[] encryptedResponse = Base64.getDecoder().decode(URLDecoder.decode(resultMatcher.group(1), StandardCharsets.UTF_8));

Con esta información encriptada, la extensión llama a decryptAES(), función la cual sigue el mismo proceso AES-CBC usado en la desencriptación de la petición.

java
byte[] decryptedResponseBytes = decryptAES(encryptedResponse, aesKey);
String decryptedResponse = new String(decryptedResponseBytes, StandardCharsets.UTF_8).trim();

Ahora que la respuesta encriptada ha sido desencriptada con éxito, la extensión comprueba si la respuesta fue más ofuscada usando ROT13. Si sí, aplica el decoding para obtener el mensaje original.

java
String finalDecryptedResponse = rot13(decryptedResponse);

Finalmente, la respuesta de autentificación desencriptada se registra en Burp Suite, permitiendo ver la respuesta pública del servidor.

java
logging.logToOutput("\n===== [Decrypted Response] =====");
logging.logToOutput(finalDecryptedResponse);
logging.logToOutput("========================================\n");

Outcome

text
===== [Decrypted Request] =====
username=ecorp_user&secret=THM{SECRET_FROM_PREVIOUS_TASK}
========================================

===== [Decrypted Response] =====
Welcome ecorp_user your secret is THM{****************************}
========================================

Después de conseguir desencriptar la respuesta al formulario, trata de cambiar el username de ecorp_user a ecopr_admin.

Modifying a Burp Extension