概述 (Overview)

时间: 2021-07-14
机器作者: guly
困难程度: easy
描述: 考察对Web系统的信息挖掘能力,并利用白盒审计寻找攻击的立足点。
Flags:User: <md5>, Root: <md5>
MACHINE TAGS:
- Web
- PHP
- Arbitrary File Upload
- Injection
攻击链 (Kiillchain)
通过使用 nmap 枚举出目标系统对外开放的服务端口,枚举Web服务的路径发现存在文件上传功能页面、站点备份压缩包。下载压缩包后对PHP代码进行白盒审计,绕过文件上传check逻辑上传PHP脚本,利用 Apache多后缀解析漏洞 执行上传的PHP脚本代码,成功获得立足点。在 guly 用户文件夹下发现 user.txt 文件,但当前shell无权限查看。分析定时任务执行脚本成功完成用户shell的横移。
执行 sudo -l ,发现可以已root身份执行的 changename.sh 脚本,使用搜索了解到网卡配置 NAME 值可注入恶意bash命令,使用该风险成功完成提权。
枚举(Enumeration)
开局使用 nmap 对目标服务器的端口进行快速的枚举:
nmap -n -Pn -p- -sV --min-rate 2000 10.10.10.146
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).
开放的服务很少,存在一个 apache 服务器,并且推断出目标系统可能是 Centos。浏览器查看后存在一段文字:

从描述中获知到是存在一个上传的,使用 dirsearch 枚举一下目录:
# Dirsearch started Wed Jul 14 00:35:23 2021 as: dirsearch.py -u 10.10.10.146 -o dirsearch.txt
301 235B http://10.10.10.146:80/backup -> REDIRECTS TO: http://10.10.10.146/backup/
200 885B http://10.10.10.146:80/backup/
200 229B http://10.10.10.146:80/index.php
200 229B http://10.10.10.146:80/index.php/login/
200 1KB http://10.10.10.146:80/photos.php
200 169B http://10.10.10.146:80/upload.php
200 2B http://10.10.10.146:80/uploads/
在 backup 页面中发现存在一个 backup.tar 的压缩包,下载后得到上诉目录的 php 源码。

立足点(Foothold)
审计 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();
}
?>
分析得出接收一个上传的文件,通过 check_file_type 函数验证下类型和文件大小,取文件后缀名判断是否为图片,最后利用 move_uploaded_file 函数保存上传的文件。
看下 check_file_type 函数是如何校验的:
function check_file_type($file) {
$mime_type = file_mime_type($file);
if (strpos($mime_type, 'image/') === 0) {
return true;
} else {
return false;
}
}
利用了 file_mime_type 方法:
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'];
}
很明显是验证了文件的 MIME 类型,在看一眼后缀名是怎么取的 getnameUpload:
function getnameUpload($filename) {
$pieces = explode('.',$filename);
$name= array_shift($pieces);
$name = str_replace('_','.',$name);
$ext = implode('.',$pieces);
return array($name,$ext);
}
这里利用文件的 MIME 类型,绕过检测判断,请求包中还需要加入Content-Type字段 image/png 值,文件首行加入 GIF89a; 绕过对上传文件类型检查的判断。

查看 photos.php 发现文件已经成功上传至服务器:

随后在此处尬住了一下午,根据版本尝试 apache httpd换行解析漏洞(CVE-2017-15715) 失败。
最后发现利用 Apache HTTPD 多后缀解析漏洞 能成功反弹shell:

apache解析文件名从右向左解析,即使最右边的文件格式在mime.types文件内,只要文件中出现.php,就可以被php模块解析。该漏洞和apache版本和php版本无关,属于用户配置不当造成的解析漏洞
AddHandler application/x-httpd-php .php。
尝试使用 md5sum 对目录文件进行比对,发现服务文件与本地一致,不存在隐藏内容,转而查找其他可疑内容。

横向移动(Lateral Movement)
在 gulu 用户下发现多个文件,其中的 user.txt 仅 gulu 用户可读,看来是需要进行横移了:

查看下 crontab.guly 和 check_attack.php 内容:

每三分钟会执行一次定时任务运行PHP脚,该脚本会读取 uploads文件夹内容,检查符合符合IP + filename 命名的文件,将其带入exec函数中去执行。结合先前的文件上传,用户是可以控制 $value` 变量的,很明显这里是存在命令注入漏洞。
scandir() 函数返回指定目录中的文件和目录的数组。
cd 的 uploads 文件夹,使用 touch 配合双引号写入nc反弹语句。等待定时任务执行,成功获得 guly 用户shell:

权限提升(Privilege Escalation)
运行 sudo -l 发现存在运行已root身份执行 changename.sh:

查看脚本内容,结合搜索搜索了解到,network-scripts 为存放对特定的网卡进行设置的配置文件,这段 bash 的含义是通过用户输入的内容生成新的网卡配置:

尝试搜索看看是否存在可用的exploit:

最终指向 https://seclists.org/fulldisclosure/2019/Apr/24 文件,从中了解到当用户可控 NAME 参数时,可以注入恶意的 bash 实现命令注入:

测试一下运行脚本,并在 NAME 中传递 /bin/id,可以看到最终回显了该命令的执行结果:

接下来就简单了,只需要传入 /bin/bash 就可以轻松实现权限提权:


参考
- https://www.freebuf.com/vuls/272174.html
- http://b.0871k.com/index.php?s=/Mobile/Show/index/cid/8/id/13.html
- https://seclists.org/fulldisclosure/2019/Apr/24