目录
Pass-11
Pass-12
Pass-13
Pass-14
Pass-15
什么鬼,我都做好了上传不成功的准备,结果sh.php居然上传成功了。
一看Response报文,果然事情没有这么简单,后端自动把文件名中的php去掉了,文件名变成sh.了
这样的话,盲猜可以用双写绕过。这关直接在repeater中试一下,就不劳intruder大驾了。
果然,双写php之后,服务器上保存的文件名是sh.php,成功绕过黑名单过滤。
代码分析:
本关代码在文件名中出现黑名单中的字符串时,用str_ireplace()函数替换为啥也没有,但是这个替换它不迭代,所以采用套娃的方式双写敏感后缀就可以绕过了。
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 . '文件夹不存在,请手工创建!';
}
}
上传sh.php之后,看这关页面返回的提示,似乎是白名单过滤呢
不死心,burp中把上图所示的报文send to repeater,发现filename改成sh.xxx上传失败,改成sh.jpg上传成功。看来真是文件名后缀的白名单过滤……
白名单还怎么玩呢(如果没有文件包含漏洞的话)……幸好这关看上去上传的文件的保存路径是用户可控的。如上图所示,请求报文的url中有个save_path参数,文件上传到服务器后的位置似乎是由这个参数决定的,这就给了%00截断发挥的机会。
构造请求报文,filename保持sh.jpg,为的是通过对文件名后缀的白名单校验;save_path的值改为 ../upload/sh.php%00,为的是使程序以为上传的文件应该保存为../upload/sh.php
虽然Response报文中显示的图片路径挺奇葩,但是后面分析代码的时候就知道显示的这个并不是文件真正的路径,实际上文件被保存为C:\phpstudy_pro\WWW\upload-labs\upload\sh.php
特别注意:
%00截断对PHP版本和配置是由要求的:
(1)PHP版本小于5.3.4
(2)php.ini中magic_quotes_gpc = Off
有两个关于%00截断的博文感觉还可以:
WEB-00截断与%00截断 | wh1te (lddp.github.io)
过气的00截断 - 云+社区 - 腾讯云 (tencent.com)
代码分析:
从下面这段代码的第3和第4行可知,文件名后缀白名单检测的位置是filename参数值,而从第6行可知,本关文件最后保存的路径是由url中save_path的值和一个随机数以及通过filename传入的文件名后缀组成的。所以本关具备%00截断的条件,即后缀白名单检测位置和最终保存位置是通过不同参数传入的。
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类型文件!";
}
}
另外,从下面的代码可知,Response报文中回显的文件路径其实只是字符串拼接后的值,并不是执行move_uploaded_file()函数之后文件的真正保存路径。
';
}
?>
和上一关一样瞅着页面提示像白名单过滤
burp中把上图所示的报文send to repeater,发现filename改成sh.xxx上传失败,改成sh.jpg上传成功,确实也是文件名后缀白名单过滤
观察到如上图所示,本关和上一关一样,请求报文中有个save_path参数,只不过位置不在url中,而是作为请求正文的一部分。那还是试试0x00截断。
像下图这样,filename设置为sh.jpg,save_path的值改为 ../upload/sh.php后面跟一个空格或者任何你知道它的十六进制ascii码的字符作为定位符。
在repeater中Request报文的Hex选项卡中找到刚刚那个定位符,修改为00,发送后发现文件上传成功
上传到服务器上的webshell文件位置如下图所示
代码分析:
睿智的你看到本关的代码一定有一阵熟悉感涌上心头。本关代码和上一关差不多,不多说了,就组装$img_path的时候取save_path的值的方法不一样,上一关是$_GET['save_path'],这关是$_POST['save_path']。
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类型文件!";
}
}
这关要求是上传图片马,然后用文件包含漏洞来利用
那就先做图片马吧。我分别做了jpeg,png和gif格式的图片马,方法是从网上分别下载三种格式的图片,然后用HxD打开(其实用notepad++打开也行),在结尾加上一句话木马
比如jpeg格式的如下,其他两种格式的类似
先来试试jpeg格式的图片马好不好使。上传 bai-shell.jpeg,上传成功。
在本关页面“文件包含漏洞”几个字上面点一下,可以得到文件包含漏洞所在url和代码,从如下代码可知,图片马可以通过file参数包含。
再获取到图片地址就可以连webshell了。图片地址可以通过查看网页源代码,或者在图片上右键复制图像链接获得。
把上面两个信息拼接一下,发现需要连接的url是http://192.168.101.16/upload-labs/include.php?file=./upload/3320211009020831.jpg
用蚁剑连接成功
其他两种格式的套路是一样的,就不详细说了,下面是png格式
血泪教训:
在上传png格式图片马的时候发现蚁剑连接不成功,直接在浏览器访问利用图片马的url发现有报错,比如
网上查了一下发现和php语法之类的有关,本来我想着用notepad++打开,把对应的行删掉,哪知道删掉一行又报另一行的错。
建议大家做图片马的时候,一定要找小一点的图片,如果找不到中意的小图,可以用在线缩小图片的工具缩一下,比如:
在线缩小图片: 高级模式 (reduction-image.com)
代码分析:
本关定义了一个getReailFileType()函数,用以检测上传的文件的头两个字节,以此判断文件是否合法,并以此决定上传后的文件的后缀名。
看了代码之后,我发现这关我自己做复杂了,其实一句话木马不需要插在真实图片里面,只要在一句话木马之前加上jpg或者gif或者png文件的前两个字节就行。
不过将木马插在真实图片中当然更好,更通用。
function getReailFileType($filename){
$file = fopen($filename, "rb");
$bin = fread($file, 2); //只读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 = "上传出错!";
}
}
}
这关我用Pass-14的三个图片马试了一下,都是可以成功上传和连接的,所以这关就不再重复说了,通关过程详见Pass-14。
代码分析:
本关用getimagesize()函数获取图像文件的大小并返回图像的尺寸以及文件类型及图片高度与宽度,并将其取出的文件类型信息$info[2]与文件后缀白名单$types进行对比,判断上传的文件是否合法,并将$info[2]作为上传后的文件的后缀名。
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 = "上传出错!";
}
}
}