ØxOPOSɆC Summer Challenge 2020 | Web Challenges Write-up

Passive-Aggressive Flask (100)
I can give you a flag, but you'll have to be nice.
Remember to say it LOUD, so I can hear you.
https://passiveaggressive.apl3b.com
~ curl https://passiveaggressive.apl3b.com
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Passive-Aggressive Flask</title>
</head>
<body>
Not so fast.
<br>
You think you can just boss me around? 😠
<br>
Be <i>polite</i>!
</body>
</html>%
Looking at the clues I was pretty sure this challenge was going to be solved by sending some sort of specific keyword, such as "please" on all caps (because of the LOUD clue), but I wasn't sure how.
After some trial and error I finally got it:
~ curl -X PLEASE https://passiveaggressive.apl3b.com
...
<body>
Oh, finally someone who shows some respect. ☺️
<br>
Here, take this juicy flag: <i>flag{g4spach0_n0_t4ch0}</i>!
<br>
(props to Pixels Camp 2020 Quiz Qualifiers <3)
</body>
</html>%
2020 (200)
Just represent the number 2020.
Easy, right? No digits allowed.
https://2020.apl3b.com

Let's take a look at the source code to see what kind of validations are taking place:
...
app.get('/', function (req, res, next) {
let output = '';
const code = req.query.code + '';
if (code && code.length < 300 && !/[^a-z().]/.test(code)) {
try {
const result = vm.runInNewContext(code, {}, { timeout: 500 });
if (result === 2020) {
output = process.env.FLAG;
} else {
output = 'nope';
}
} catch (e) {
output = 'nope';
}
} else {
output = 'nope';
}
res.render('index', { title: '[a-z().]', output });
});
...
So, basically we needed to find a piece of JS code that:
- was under 300 characters;
- matched this REGEX:
!/[^a-z().]/
; - produced the number
2020
; - used functions and variables known to the
NodeJS
context.
This last condition ended up being the hardest, and I had to rely pretty heavily on the eval
function:
eval.length.constructor( // Number()
eval.name.constructor.apply() // String()
.concat(eval.name.big.apply.length) // 2
.concat(eval.name.bold.length) // 0
.concat(eval.name.big.apply.length) // 2
.concat(eval.name.bold.length)) // 0
Alternatively I found one that's a little longer:
eval.length.constructor(
eval.name.constructor.apply()
.concat(((typeof(null))
.concat((typeof(null)))
.concat(typeof(eval)).length))
.concat(((typeof(null))
.concat((typeof(null)))
.concat(typeof(eval)).length)))

Extensible Risky Language (300)
Your flag is securely stored in/etc/passwd
.
https://risky.apl3b.com
I smell an XXE
attack!
When we submit our shoe size, the size is reflected below the input field, so I immediately thought the idea was to manipulate the XML
sent to the server in order to fetch the /etc/passwd
file.

If this wasn't enough of a clue, it looks like the devs are not great at XML.

And indeed when we send a POST
request to https://risky.apl3b.com/process with our shoe size, the form data is sent in XML:
xml: <stock><size>34</size></stock>
Let's exploit this vulnerability to get the content of the /etc/passwd
file:

And we get our flag: flag{g3lat1na_r0y4l_m0raNg0}
.