This is a "security scanner" in PHP which, given a URL of a git repository, applies a heuristic algorithm that checks if thereany security vulnerabilites in the code:```phpif(TRUE) { // patented algorithm (tm)    echo 'Likely insecure :('; }```...hard to argue with that!
Once again, our goal is to execute the `/readflag` binary on the server to read the flag.
The PHP application visits the given URL twice:  1. (using `file_get_contents(URL)`) To validate that this is a legitimate git server. The validation results for all URLs are stored in memcached.  2. (using `git ls-remote`) To actually fetch the tags of the repository.
Here's a visualization of what happens when we put a legitimate git repository in the scanner:
![Works as intended](good_workflow.png)
Since `git ls-remote` is run with `exec()`, a rogue "URL" in memcached is an instant Game Over for the challenge authors:
![Uh-oh](bad_workflow.png)
How can we successfully "validate" `;/readflag`, though? Initially it seems like we can't, because:  1. A valid URL should match `/^[0-9a-zA-Z:\.\/-]{1,64}$/`, and `;/readflag` definitely doesn't. The code uses `preg_match()`, so we can't  use UTF-8 shenanigans as in [heiko](shenanigans), either.  2. Even if the regular expression matched, `;/readflag` is not a valid URL that `file_get_contents()` would recognize. The good news is the memcached protocol is [notoriously vulnerable](https://www.blackhat.com/docs/us-14/materials/us-14-Novikov-The-New-Page-Of-Injections-Book-Memcached-Injections-WP.pdf) to injections.Essentially, if we can make either `file_get_contents()` or `git ls-remote` connect to `127.0.0.1:11211` and send some `\r\n`'s, we win.
Smuggling CRLFs into memcached proves to be surprisingly tricky:  * HTTP(S) implementations both in PHP and libcurl (which is what `git` uses under the hood) have protections against request splitting  and won't let us use `\r\n` in a valid HTTP request. We tried various ways, and none of those worked.  * FTP is of not much help here, unlike in [resonator](https://github.com/dfyz/ctf-writeups/tree/master/hxp-2020/heiko). While we still redirect the  FTP client to 127.0.0.1, this time we can only retrieve data, not store it.  * Smuggling newlines in the FTP username fails, too, due to the regexp that validates the URL.
Just when it seems that all hope is lost, [TLS Poison](https://github.com/jmdx/TLS-poison) appears! Brifely, a TLS server mayset a session ID for the client, which the client may pass to the server on subsequent connections to re-use the same premastersecret they had used before to encrypt the traffic. This ID is stored in the clear in the very first TLS handshake packet the clientsends to the server.
However, most TLS stacks only look at the host/port when determining ifa session ID should be reused. This means that if a malicious HTTPS server redirects the client to itself, but the same host now suddenlyresolves to `127.0.0.1`, the client will still happily send the session ID to whatever local service is listening locally on this port(spoler: it's memcached). The session ID is just random 32 bytes, so we can easily pack `\r\n` into it.
`file_get_contents()` in PHP doesn't seem to re-use TLS session IDs, so our implementation of this attack targets git/libcurland does it little bit differently from vanilla TLS Poison:
![Our attack](tls_poison.png)
First, a [custom rustls fork](https://github.com/jmdx/TLS-poison/tree/master/rustls) is an overkill for CTF purposes. If we're willingto cut some corners (e.g., don't use ephemeral Diffie-Hellman or any extensions), we can create a simple TLS server in [~200 line of Python](https://github.com/dfyz/ctf-writeups/blob/master/hxp-2020/security%20scanner/fake_git.py#L196).
Second, libcurl aggressively caches DNS lookup results for 60 seconds, even for DNS records with zero TTL. This meansthe standard DNS rebinding technique doesn't work, since curl will just reconnect to the same server after a HTTP redirect.To overcome this, we return two A records for our fake git servers: the first one is a real IP address, the second one is `0.0.0.0` (incidentally, this means we don't really need a custom DNS resolver at all, but hey, it's only [~100 lines](https://github.com/dfyz/ctf-writeups/blob/master/hxp-2020/security%20scanner/fake_dns.py) of Python). After serving the HTTP redirect, we immediately shutdown the server, so that libcurl thinks it's dead and tries `0.0.0.0` instead, which will lead it to memcached.
Note that we can't return an A record with `127.0.0.1` directly. `getaddrinfo()`reorders IPv4 addresses according to section 3.2 of [RFC 3484](https://www.ietf.org/rfc/rfc3484.txt), which results in libcurl ignoring the real IP address and always connecting to `127.0.0.1`.
Armed with fake DNS and fake TLS, we [obtain the flag](https://github.com/dfyz/ctf-writeups/blob/master/hxp-2020/security%20scanner/exploit.py):```PS> python .\exploit.py [REDACTED_IP]Got PHP cookie 3kdatudleb9qsarh0gbinjatq3 and sandbox id TkUroC3hS68xigPoisoning memcachedMemcached is poisoned, reading flaghxp{Bundesamt_fuer_Sicherheit_in_der_Informationstechnik_(_lol_)_would_approve}```
For completeness, here is the output from FakeGIT:```Welcome to FakeGITGot a connection from [REDACTED]Got client helloSent server hello with session id b''Sent 2 certificatesSent server hello doneGot a premaster secretGot client finishedSent server finished, the connection is readyGot a message of length 118This is PHP! Showing them something that looks like a git repo and stealing sandbox IDGot sandbox id: b'NE+\xa0-\xe1K\xaf1\x8a', session_id: b'\r\nset NE+\xa0-\xe1K\xaf1\x8a;/r* 0 0 2\r\nOK\r\n'Sent a message of length 118Got a connection from [REDACTED]Got client helloSent server hello with session id b'\r\nset NE+\xa0-\xe1K\xaf1\x8a;/r* 0 0 2\r\nOK\r\n'Sent 2 certificatesSent server hello doneGot a premaster secretGot client finishedSent server finished, the connection is readyGot a message of length 186This is git! Redirecting it back to memcached and shutting downSent a message of length 138Laying low for 5 seconds so that the git client doesn't reconnect to usWelcome to FakeGIT```
P.S. Some of the minor details were omitted for clarity in this writeup:  * the memcached instance was shared between the teams during the CTF, so the security scanner prepends a "sandbox ID" at the beginning of memcached keys.  The sandbox ID is different for each team and is stored in the PHP session;  * with the sandbox ID in place, `;/readflag` is actually too long to fit in the TLS session id and has to be shortened to `;/r*` (we're lucky that there are no other binaries starting with `r` in `/`).  * the organizers told us the intended solution for this challenge was using FTPS instead of HTTPS. This is left as an exercise for the reader. :-)