xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackTheBox

Trapped Source



Enumeración


En la página web bastante simple la cual nos pide un pin para desbloquear algo

Al enviar cualquier pin incorrecto nos devuelve el mensaje INVALID! y no hace nada

Con Ctrl + U podemos ver el codigo fuente y en una parte de el podemos ver un campo correctPin con un pin posiblemente valido para la web 8291


Explotación


Al enviar el codigo 8291 en la web vemos que es válido, que nos devuelve la flag

Solo como extra para entender el ¿porque? analicemos el codigo para ver como hace la comparación que desbloquea la flag, como crea la petición y tal

❯ curl -s http://165.227.224.40:32167
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="/static/css/style.css">
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">

</head>

<body>
    <script>
        window.CONFIG = window.CONFIG || {
            buildNumber: "v20190816",
            debug: false,
            modelName: "Valencia",
            correctPin: "8291",
        }
    </script>
    <div class="lockbox">
        <div class="lockStatus">LOCKED</div>
        <div class="lockMid">
            <span id="btn9" class="button" onclick="unlock(9)">9</span>
            <span id="btn8" class="button" onclick="unlock(8)">8</span>
            <span id="btn7" class="button" onclick="unlock(7)">7</span>
            <span id="btn6" class="button" onclick="unlock(6)">6</span>
            <span id="btn5" class="button" onclick="unlock(5)">5</span>
            <span id="btn4" class="button" onclick="unlock(4)">4</span>
            <span id="btn3" class="button" onclick="unlock(3)">3</span>
            <span id="btn2" class="button" onclick="unlock(2)">2</span>
            <span id="btn1" class="button" onclick="unlock(1)">1</span>  
            <div class="clear" onclick="reset()">Clear</div>
            <div class="scoreCount" onclick="checkPin()">Enter</div>
        </div>
    </div>

    <!-- partial -->
    <script src='/static/js/jquery.js'></script>
    <script src="/static/js/script.js"></script>
</body>

</html>

En el html casi al final podemos ver que llama a script.js asi que veamoslo

❯ curl -s http://165.227.224.40:32167/static/js/script.js     
currentPin = []

const checkPin = () => {
    pin = currentPin.join('')

    if (CONFIG.correctPin == pin) {
        fetch('/flag', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'  
            },
            body: JSON.stringify({
                'pin': CONFIG.correctPin
            })
        })
        .then((data) => data.json())
        .then((res) => {
            $('.lockStatus').css('font-size', '8px')
            $('.lockStatus').text(res.message)
        })
        return
    }

    $('.lockStatus').text('INVALID!')
    setTimeout(() => {
        reset()
    }, 3000)

}

const unlock = (pin) => {
    currentPin.push(pin)

    if (currentPin.length > 4) return

    $('.lockStatus').text(currentPin.join(' '))
}

const reset = () => {
    currentPin.length = 0
    $('.lockStatus').css('font-size', 'x-large')

    $('.lockStatus').text('LOCKED')
}

Analicemos poco a poco, en el html se abre una etiqueta script la cual declara un objeto global CONFIG la cual tiene definidas varias propiedades interesantes

<script>
    window.CONFIG = window.CONFIG || {  
        buildNumber: "v20190816",
        debug: false,
        modelName: "Valencia",
        correctPin: "8291",
    }
</script>

El script.js consta de varias funciones con tareas especificas que analizaremos, pero globalmente lo que hace es definir la variable currentPin como un array vacio

currentPin = []  

En el html podemos ver que en cada numero que aparece al ingresar el pin al hacer clic en el llama a la función unlock pasandole el numero como argumento

<span id="btn9" class="button" onclick="unlock(9)">9</span>
<span id="btn8" class="button" onclick="unlock(8)">8</span>
<span id="btn7" class="button" onclick="unlock(7)">7</span>
<span id="btn6" class="button" onclick="unlock(6)">6</span>
<span id="btn5" class="button" onclick="unlock(5)">5</span>
<span id="btn4" class="button" onclick="unlock(4)">4</span>
<span id="btn3" class="button" onclick="unlock(3)">3</span>
<span id="btn2" class="button" onclick="unlock(2)">2</span>
<span id="btn1" class="button" onclick="unlock(1)">1</span>  

Esta función esta definida en el js y lo que hace es agregar el numero del argumento al array definido antes, esto hasta que la longitud sea menor de 4 digitos, seguido de esto muestra en el navegador los valores juntos es decir el pin ingresado

const unlock = (pin) => {
    currentPin.push(pin)

    if (currentPin.length > 4) return

    $('.lockStatus').text(currentPin.join(' '))  
}

Hay un botón llamado clear en la web que al hacer clic llama a la función reset

<div class="clear" onclick="reset()">Clear</div>  

Esta función se encarga de definir la longitud del array a 0 es decir que elimina los valores ingresados, seguido de eso define la fuente y muestra LOCKED por pantalla

const reset = () => {
    currentPin.length = 0
    $('.lockStatus').css('font-size', 'x-large')  

    $('.lockStatus').text('LOCKED')
}

Al hacer clic en el botón que se muestra como Enter llama a la función checkPin

<div class="scoreCount" onclick="checkPin()">Enter</div>  

Esta función se encarga de compactar el array de el pin que ingresamos a una string es decir de algo como [1,2,3,4] a 1234, seguido de eso compara este pin con la propiedad correctPin definida en el objeto global CONFIG definido en el html

const checkPin = () => {
    pin = currentPin.join('')

    if (CONFIG.correctPin == pin) {  

Si la condición se cumple y el pin es correcto este hace una petición por POST a /flag, donde en el Content-Type especifica que envia un json, todo esto con una data en json donde el pin tiene como valor la propiedad correctPin

if (CONFIG.correctPin == pin) {
fetch('/flag', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'  
    },
    body: JSON.stringify({
        'pin': CONFIG.correctPin
    })
})

Despues recibe una data en json y el atributo message lo muestra en la página

.then((data) => data.json())
.then((res) => {
    $('.lockStatus').css('font-size', '8px')  
    $('.lockStatus').text(res.message)
})

En caso de que el pin no coincida mostrara INVALID! y llamara a la funcion reset

$('.lockStatus').text('INVALID!')  
setTimeout(() => {
    reset()
}, 3000)

Hagamos la petición con curl por POST hacia /flag con la data y el Content-Type como se especifica en el js, nos devuelve un json y en el atributo message la flag

❯ curl -s http://165.227.224.40:32167/flag -H 'Content-Type: application/json' -d '{"pin": "8291"}' | jq  
{
  "message": "HTB{vi3w_cli13nt_s0urc3_S3cr3ts!}"
}