#index.php
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess");
$file_name = trim($_FILES['upload_file']['name']); //去除首尾空格
$file_name = deldot($file_name);//删除文件名末尾的点
$file_ext = strrchr($file_name, '.'); //取后缀名
$file_ext = strtolower($file_ext); //转换为小写
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$file_ext = trim($file_ext); //首尾去空
if (!in_array($file_ext, $deny_ext)) {
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $_FILES['upload_file']['name'])) {
$img_path = $UPLOAD_ADDR . '/' . $file_name;
$is_upload = true;
}
} else {
$msg = '此文件不允许上传';
}
} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
从上面的代码可以看出,前面几乎所有的过滤姿势都用上了,貌似没有办法绕过了。
但是细心审计代码发现,原来天机在deldot()函数中:
#common.php
function deldot($s){
for($i = strlen($s)-1;$i>0;$i--){
$c = substr($s,$i,1); //substr(string,start,length)第一个字符从0开始
if($i == strlen($s)-1 and $c != '.'){
return $s;
}
if($c != '.'){
return substr($s,0,$i+1);
}
}
}
?>
函数没啥问题,就是循环检查,如果末尾是点就向前移一位,知道不是点了就取该位置之前的字符串。
这里的漏洞在于,参数在进行层层过滤的最后没有再次使用deldot()函数来去除末尾的点。
所以可以构造文件名为muma.php. .
,也就是php后面加点空格点空格,这样经过上面代码层层过滤后变为.php.
,可以成功绕过黑名单,并且利用windows特性,在保存文件时会自动去除后缀名末尾的点使得apache成功解析。
老规矩,先尝试上传一个木马文件:
用burp抓包,将后缀名改为.php. .
重放,上传成功!
查看上传文件:
果然windows很贴心的去除了php后面的点。
用菜刀连接即可。
这一关属于过滤的逻辑处理不当造成的漏洞,没有对后缀的点循环过滤。
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists($UPLOAD_ADDR)) {
$deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
$file_name = trim($_FILES['upload_file']['name']); //去除首尾空格
$file_name = str_ireplace($deny_ext,"", $file_name); //将黑名单中的字符串替换为空
if (move_uploaded_file($_FILES['upload_file']['tmp_name'], $UPLOAD_ADDR . '/' . $file_name)) {
$img_path = $UPLOAD_ADDR . '/' .$file_name;
$is_upload = true;
}
} else {
$msg = $UPLOAD_ADDR . '文件夹不存在,请手工创建!';
}
}
由上述代码可知,后端将上传的文件名后缀会根据一个黑名单进行匹配,并且替换为空,而且是一次替换,所以将后缀名双写即可绕过。
先尝试上传:
用burp抓包,将后缀php双写:
重放,上传成功!但是后缀被改成了hpp???什么情况?
仔细一想哦,替换的逻辑是从前往后替换的,.phphpp
先将前面的php替换为空,然后剩下hpp
所以换一个双写的位置:.pphphp
这样应该就没问题了
果然成功!然后用菜刀连接即可。
这一关的过滤逻辑与前面不同,是将敏感的后缀名替换为空,然后不管合不合法也会上传成功,在实际开发使用中会产生大量的垃圾文件占用存储空间,而且也会存在潜在的安全隐患,比如如果存在文件包含漏洞,该木马文件依然会被利用。
而这一关只是将敏感的后缀一次性替换为空,所以双写即可绕过,不过要注意双写的位置和替换的逻辑顺序。
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif'); //设置白名单
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); //取后缀名
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; //截断就在这里发生
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
}
else{
$msg = '上传失败!';
}
}
else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
这里采用白名单机制,若后缀名为jpg、png、gif中的一种,则将文件改名并保存。
先上传一个php文件,发现只允许上传png、jpg、gif后缀的文件,据此判断因该是白名单过滤。
用burp抓包看一下:
如图所示,发现上传文件的保存路径是用post请求中提交上去的,那就说明路径可控,可以尝试用%00截断绕过。
%00是什么?
%00是NULL的十六进制表示形式,在操作系统读取文件时,当遇到NULL时就会认为文件已经结束,后面的内容将不再读取。
%00截断绕过的条件:
1、php版本小于5.3.4
2、magic_quotes_gpc=Off
magic_quotes_gpc(魔术引号开关)函数,当magic_quotes_gpc=On时在php中的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据的特殊字符(’、"、\、NULL)加上反斜线进行转义。
%00截断绕过的原理:
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
这句php代码是用来设置上传文件保存的路径的,在正常情况下路径会这样设置
../upload//随机数+时间qianxun.jpg
但是如果文件保存的路径可控,我们可以把路径改为
../upload/1.php%00
然后传到后台拼接的逻辑为
../upload/1.phpNULL/随机数+时间qianxun.jpg
但是在操作系统读取的时候,由于NULL的截断,所以只会读到
../upload/1.php
截至此操作就相当于将上传的文件qianxun.jpg保存为
../upload/1.php
因为qianxun.jpg只是改了后缀的木马文件,此时又因为截断绕过将文件重命名为1.php,所以可以成功被apache所解析。
开干!!
修改路径为../upload/1.php%00
,重发:
结果报错了,我们可以看一下报错信息,发现0被转义了。。。
可能是因为magic_quotes_gpc处于开启状态,为了复现漏洞,在php配置文件中更改magic_quotes配置为off并保存,然后重启apache。
再次尝试:
如图所示,上传成功!
查看上传的文件:
用浏览器访问文件:
%00截断使用的场景:
多数情况下都是抓包改文件名进行测试,在文件名后缀末尾加hex形式的%00,例如:filename=‘1.php[null].jpg’,但是在php中,
$FILES['file']['name']
在得到文件名时就已经被截断了,变为1.php
所以无法逃过后端if(in_array($file_ext,$ext_arr))
的过滤。还有一种情况:
如果文件名是通过url获取的,并且后台保存文件时是用
$_REQUEST['file_name']
或$_GET['file_name']
获取文件名拼接在路径后面的,比如:$file_path="upload/".$_REQUEST['file_name'].rand(10,99).date("YmdHis").".".$file_ext;
,此时只需要将url参数中的值改为file_name=1.php%00.jpg
,然后直接上传木马文件后缀改jpg;到后端的逻辑是,木马图片因为后缀是.jpg所以可以通过白名单过滤,好戏来了,$_REQUEST['file_name']
得到的值为1.php%00.jpg
,拼接在路径后就是upload/1.php[null].jpg+随机数+时间+后缀名
,由于null的截断,所以最后保存的文件路径为upload/1.php
。相当于将图片马直接改名为1.php,使apache成功解析!再一种情况:
就是本关的情况,保存路径是从url传进去的,所以用户可控,直接将%00添加在路径中,保存时截断,效果异曲同工。
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1);
if(in_array($file_ext,$ext_arr)){
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
//将post传递的文件保存路径直接拼接到了最终的路径中
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
}
else{
$msg = "上传失败";
}
}
else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
上传一个木马文件:
用burp抓包:
发现文件的保存路径是由post传递的。
尝试使用%00截断方法:
将+的十六进制2b改为null的十六进制00,回车。
因为这一关文件的路径是通过post请求传递的,所以不能直接使用%00,因为后台不会自动解码,所以要添加hex形式的00。
更改前 | 更改后 |
---|---|
放包之后,如图所示上传成功!
用菜刀连接:
成功!
这一关和上一关还是大同小异,只是路径的传递方式不同,也就意味着00的形式不同。上一关是GET传递,所以要用%00,这一关是POST传递,所以要用hex形式的00。
我觉得判断是否存在00截断绕过的可能,关键点在于后台上传文件保存的路径用户是否可控,无论是用户传入的文件名还是保存路径,是否直接拼接到了最终的路径中,若是那就危险了。