Ø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}.