mephi42: (Default)
[personal profile] mephi42
Caught flu and staying at home, drinking gallons of warm tea. The fever is thankfully gone (but not cough, goddamnit!), so this is a good opportunity to do whatever I want, which in this case is solving some CTF tasks.

Let's give the "Looking glass" from Teaser Dragon CTF 2019 a try. The CTF is over, but who cares - the services are still running. The objective is to read the /flag file from the server on which http://lg.hackable.software:8080/ is running. The sources are available.


What we have is a web service written in Go, which takes a ProtoBuf-encoded request to ping a specified server, and returns a ping command output in a ProtoBuf-encoded response. Pinging is carried out as follows:

                commandline = fmt.Sprintf("ping -%d -c %d %s", c.PingCommand.GetIpVersion(), c.PingCommand.GetCount(), c.PingCommand.GetAddress())
...
        e := exec.CommandContext(ctx, "/bin/sh", "-c", commandline)


so one would be tempted to ping ;cat /flag, however, there is a validation routine preventing that. Validation results are cached:

        key := md5bytes(data)
...
        valid, ok := v.cache.Get(key)
        if ok && valid.(bool) {
                return &cmd
        } else if checkAddress(address) {
                v.cache.Add(key, true)
                return &cmd
        }


This reveals the gist of the task: construct two ProtoBuf messages with colliding MD5 checksums, such that the first one passes validation, and the second one contains the injection.

Quick googling produces the following useful links:



Attempt #1: Trying to find a collision for messages with fully specified addresses using cpc.sh script, for example:


  • b'\x0A\x3E' - Ping message with a total length of 0x40 (hashclash outputs files whose size is multiple of 0x40)

  • b'\x0A\x09127.0.0.1' - address='127.0.0.1'

  • b'\x22\x31' - Non-existent string field with id 4, whose value is to be filled by hashclash and whose value ProtoBuf will ignore



and


  • b'\x0A\x3E'

  • b'\x0A\x0A;cat /flag'

  • b'\x22\x30'



After half an hour of waiting, I realize this might not be practical on my 4-core i5. I briefly toy with the idea of doing this on something like a spot c5.24xlarge instance for ~1.5$/hour, but surely this is not something authors had in mind?! ("briefly" is actually a lie: I must have spent about an hour preparing a Docker image and requesting increases of various AWS limits before coming to my senses)

hashclash can make use of CUDA, but WSL does not support it, so I have to install Linux - a long overdue TODO anyway. md5_birthdaysearch finishes very quickly, getting my hopes up, but the next stages run on CPU again.

Attempt #2: Having studied corkami/collisions a little bit more carefully, I find Unicoll. What it does is: you specify a 12-byte prefix, it flips the least significant bit of the 10th byte, and finds the colliding messages, one of which has the original prefix, and the other one has the modified prefix. A quick try with some random data shows that hashclash can find such collisions in 3.5 minutes.

I also read https://news.ycombinator.com/item?id=13826734, and learn another important property of MD5: if md5(A) = md5(B), provided A and B's lengths are multiples of 64, then hash(A + C) = hash(B + C).

So the message structure to work with is:


  • Controlled 12-byte prefix

  • Hashclash-generated 52 (or, more realistically, 52+64) bytes

  • Controlled suffix with a controlled length



Which ProtoBuf field has the most useful least significant bit?


  • Address value: allowed characters are "abcdefghijklmnopqrstuvwxuz.1234567890", and, judging by ASCII table, flipping their least significant bits does not produce anything useful (b';', for example, would have been nice)

  • Wire format: converting e.g. a string to a start group is of no use

  • Field length: changing address length is not particularly useful, since more than one address character is needed to achieve the goal. Changing padding field length is, on the other hand, very promising. One could embed injection into padding, and then flip the bit to make it a part of an actual address field!



This finally yields a working solution:


  • b'\x0A\xB0\x01' - Ping message with a total length of 177

  • b'\x22\x03\xAA\xBB\xCC' - First padding field

  • b'\x2A\x76\xDD\xEE' - Padding field that covers hashclash-generated junk; after flipping its length's LSB, it will cover 1 extra byte

  • 52+64 hashclash-generated bytes. If hashclash generates just 52, b'\xFF' * 64 can be appended

  • b'\x0A\x0A' + b'\x30' * 0x0A - Address field for the good message.

  • b'\x32\x25;cat /flag' + b'\x20' * 0x1c - Padding field containing the injected address



What happens with the last two fields after a bit flip? The first byte is dropped, and we get:


  • b'\x0A\x30' + b'\x30' * 0x09 - Address field with a length of 0x30 starting with 000000000

  • b'\x32\x25;cat /flag' + b'\x20' * 0x1c - Continuation of the address field - its full value is 0000000002%;cat /flag



So, having generated collision1.bin and collision2.bin, we can append various suffixes containing the commands to run, and execute them as follows:



Done! A few lessons learned:


  • Have the infrastructure/tools ready. Were it a real CTF, downloading and installing Linux would've set me back significantly

  • Iterate fast, don't cling to ideas

  • Spend a bit more time thinking/planning before mashing the buttons. E.g. Unicoll could've been found much earlier and AWS "adventure" could've been completely avoided



Writeups from another people:



This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

Profile

mephi42: (Default)
mephi42

September 2019

S M T W T F S
1234567
891011121314
15161718192021
22232425 262728
2930     

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 10th, 2025 03:24 pm
Powered by Dreamwidth Studios