目录
- 不同类型的语言
- 脚本语⾔/解释型语⾔
- ⼀次编译到处运⾏
- 编译型语⾔
- 不同语⾔的webshell上传差异
- 脚本语⾔/解释型语⾔
- ⼀次编译到处运⾏
- 编译型语⾔
- ⽂件上传到webshell
- 任意⽂件上传
- js检测
- 解析规则
- MIME
- ⽂件头
- 后缀检测失效
- NTFS Tricks
不同类型的语言
脚本语⾔/解释型语⾔
代表就是php/jsp/asp这种,他们的运⾏模式是⼀套动态解释引擎+脚本代码,在这种运⾏模式
下,我们⼀旦能够获取cmdshell或者物理设备,等同于我们能看到全部的代码。
所以对于php/jsp/asp⽽⾔,简单记为我们能看到所有的代码,是纯⽩盒审计。
⼀次编译到处运⾏
代表就是java/python/.net
他们的运⾏模式是将代码编译成中间语
⾔,并且在不同的操作系统上运⾏有不同的虚拟机,由虚拟机来完成将中间语⾔(ir)run起
来的操作。因为其强⼤的跨平台能⼒,也迅速得到推⼴。
对于java/python/.net这种虚拟机+字节码的运⾏模式,我们简单记为,可以恢复并看到
80%以上的代码来进⾏审计,基本等于⽩盒审计。
编译型语⾔
C/go/c++
对于编译型语⾔,基本看不到代码。
不同语⾔的webshell上传差异
脚本语⾔/解释型语⾔
可以上传webshell并运⾏
⼀次编译到处运⾏
视情况⽽定:
python基本不能直接上传webshell,因为没办法路由到webshell⽂件。
java要看中间件,有的中间件解析jsp的情况下,是可以通过上传jsp的webshell,有的中间件
不解析jsp就没办法了。
.net也是⼀样的道理,但是从经验来看,.net搭建的⽹站很多可以直接上传aspx的webshell。
编译型语⾔
没有办法上传webshell,但是在某些情况下可以通过上传+其他利⽤来进⾏rce。
⽂件上传到webshell
⽂件上传能够到webshell,归根结底是2个层⾯的问题:后缀处理、运⾏环境(操作系统、中
间件)
任意⽂件上传
php"><?php
// 设置内容类型和字符编码
header("Content-type: text/html;charset=utf-8");
// 关闭错误报告
error_reporting(0);// 设置上传目录
define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
// 设置上传目录的URL路径
define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));$is_upload = false;// 检查上传目录是否存在,如果不存在则创建
if (!file_exists(UPLOAD_PATH)) {mkdir(UPLOAD_PATH, 0755);
}// 检查是否提交了表单
if (!empty($_POST['submit'])) {// 检查是否有文件上传if (!$_FILES['file']['size']) {echo "<script>alert('请添加上传文件')</script>";} else {// 获取上传文件的文件名$name = basename($_FILES['file']['name']);// 移动上传的文件到指定目录if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true;} else {echo "<script>alert('上传失败')</script>";}}
}
?>
这是⼀个最简单的任意⽂件上传的原型,上传来的⽂件只是取了下⽂件名,就通过
move_uploaded_file写⼊到
UPLOAD_PATH . $name 去了。没有任何检查。
js检测
php"><?php
header("Content-type: text/html;charset=utf-8");
error_reporting(0);// 设置上传目录
define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));$is_upload = false;// 检查上传目录是否存在,如果不存在则创建
if (!file_exists(UPLOAD_PATH)) {mkdir(UPLOAD_PATH, 0755, true); // 添加了第三个参数true,以允许递归创建目录(如果需要的话)
}// 处理文件上传
if (!empty($_POST['submit'])) {if ($_FILES['file']['size'] == 0) {echo "<script>alert('请添加上传文件')</script>";} else {$name = basename($_FILES['file']['name']);if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true;} else {echo "<script>alert('上传失败')</script>";}}
}
?><!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>JavaScript 绕过</title><link href="./attachs/bootstrap.sketchy.min.css" rel="stylesheet">
</head>
<body><div class="container"><div class="jumbotron"><h1 class="text-center">永远不要相信用户的输入</h1><img src="./imgs/js.png" class="rounded mx-auto d-block" width="auto"><br><p class="lead">“永远不要相信用户的输入” 是进行安全设计和安全编码的重要准则。换句话说,任何输入数据在证明其无害之前,都是有害的。许多危险的漏洞就是因为过于相信用户的输入是善意的而导致的。</p><br><div><?phpif ($is_upload) {echo '<img src="./upload/' . $name . '" class="rounded mx-auto d-block" width="100px">';}?></div><form action="" method="post" enctype="multipart/form-data" onsubmit="return checkfilesuffix()"><div class="form-group"><label for="exampleFormControlFile1">文章插入图片</label><input type="file" class="form-control-file" name="file" id="file"><input type="submit" name="submit" value="Upload"></div></form></div></div><script>function checkfilesuffix() {var file = document.getElementsByName('file')[0].value;if (file == "" || file == null) {swal("请添加上传文件", "", "error");return false;} else {var whitelist = new Array(".jpg", ".png", ".gif", ".jpeg");var file_suffix = file.substring(file.lastIndexOf("."));if (whitelist.indexOf(file_suffix) == -1) {swal("只允许上传图片类型的文件!", "", "error");return false;}}return true; // 添加了返回true,以确保在文件后缀合法时表单能够提交}function error() {swal("上传失败", "", "error");}</script><script src="./attachs/sweetalert.min.js"></script>
</body>
</html>
在整个输⼊流转中,真正决定⽂件是否写⼊的是后端代码路由,⽽从它往前数,⽹络层⾯我们
是可控的,所以完全不必要去管js的事情。
解析规则
不同中间件不同版本有不同的利⽤⽅式
MIME
php"><?php
header("Content-type: text/html;charset=utf-8");
error_reporting(0); // 通常不建议在生产环境中完全关闭错误报告// 设置上传目录
define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
// 注意:UPLOAD_URL_PATH的定义可能不正确,它应该是一个相对于网站的URL路径
// 但当前代码尝试从服务器路径中移除DOCUMENT_ROOT,这通常不会得到正确的URL
define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));if (!file_exists(UPLOAD_PATH)) {mkdir(UPLOAD_PATH, 0755); // 创建上传目录,权限设置为755
}$is_upload = false;if (!empty($_POST['submit'])) {// 检查文件类型是否为允许的图像类型if (!in_array($_FILES['file']['type'], ["image/jpeg", "image/png", "image/gif", "image/jpg"])) {// 注意:echo中的black()函数未定义,可能是想调用alert()或其他函数echo "<script>alert('文件类型不允许');</script>"; // 修改为alert,并给出明确的错误信息exit();} else {$name = basename($_FILES['file']['name']); // 获取上传文件的原始名称// 将文件从临时目录移动到指定上传目录if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true; // 标记文件上传成功} else {echo "<script>alert('上传失败');</script>"; // 文件上传失败时给出提示}}
}
?>
⽂件头
php"><?php
header("Content-type: text/html;charset=utf-8");
// 关闭错误报告(不推荐在生产环境中这样做)
error_reporting(0);// 设置上传目录
define("UPLOAD_PATH", dirname(__FILE__) . "/upload/");
// 注意:UPLOAD_URL_PATH的定义可能不正确,应该根据实际情况设置
define("UPLOAD_URL_PATH", str_replace($_SERVER['DOCUMENT_ROOT'], "", UPLOAD_PATH));// 如果上传目录不存在,则创建它
if (!file_exists(UPLOAD_PATH)) {mkdir(UPLOAD_PATH, 0755);
}$is_upload = false;if (!empty($_POST['submit'])) {// 检查文件大小是否为0if (!$_FILES['file']['size']) {echo "<script>alert('文件大小为0,上传失败');</script>";exit();}// 读取文件的前4个字节(文件头)$file = fopen($_FILES['file']['tmp_name'], "rb");$bin = fread($file, 4);fclose($file);// 检查文件类型(通过MIME类型和文件头)$allowedMimeTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif"];$allowedFileHeaders = ["89504E47", "FFD8FFE0", "47494638"]; // 分别对应PNG, JPEG, GIF的文件头if (!in_array($_FILES['file']['type'], $allowedMimeTypes)) {echo "<script>alert('文件类型不允许');</script>";exit();} elseif (!in_array(bin2hex($bin), $allowedFileHeaders)) {echo "<script>alert('文件头不匹配,可能是非法文件');</script>";exit();}// 获取原始文件名$name = basename($_FILES['file']['name']);// 移动上传的文件到指定目录if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true;} else {echo "<script>alert('文件上传失败');</script>";}
}
?>
我们知道,⽂件头是⽂件格式的普适约定,⼀般在⼀个⽂件的前⼏个字节。
但是对于php/asp/jsp这种解释型语⾔⽽⾔,灵活性很强,并不要求脚本代码位于⽂件的开头,这样的话,我们就可以在开头使⽤图⽚⽂件的⽂件头,在中间或者后⾯的位置插⼊脚本语⾔,进⾏getshell。
与上⾯类似的,这种检测⽅式也没有触及到核⼼,并没有去检测filename字段的后缀。
后缀检测失效
后缀检测失效,指的是程序员已经想到要检测filename字段的后缀了,但是因为业务能⼒不熟练,在检测过程中被绕过了,导致webshell上传。
php"><?php
// ...(之前的代码,如header设置、错误报告关闭、上传目录定义等)if (!empty($_POST['submit'])) {// 获取上传文件的原始名称$name = basename($_FILES['file']['name']);// 定义黑名单,包含不允许上传的文件扩展名$blacklist = array("php", "php5", "php4", "php3", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess", "ini");$name = str_ireplace($blacklist, "", $name);// 检查文件是否成功上传if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true;} else {// 上传失败,显示错误消息echo "<script>alert('文件上传失败');</script>";}
}
?>
将敏感后缀字符替换为空,那么就可以⽤pasxhp–>php来绕过
php"><?php
// 假设 UPLOAD_PATH 已经被定义为一个安全的上传目录路径if (!empty($_POST['submit'])) {// 获取上传文件的原始名称$name = basename($_FILES['file']['name']);// 定义黑名单,包含不允许上传的文件扩展名$blacklist = array("php", "php5", "php4", "php3", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess", "ini");// 逐个替换黑名单中的扩展名为空格(注意:这种方法不安全,应该使用白名单)foreach ($blacklist as $extension) {$name = str_replace('.' . $extension, ' ', $name); // 注意添加点号来匹配完整的扩展名}// 移除文件名中多余的空格(可能由于多个扩展名被替换而产生)$name = preg_replace('/\s+/', '_', $name); // 使用下划线替换空格,或者您可以选择删除它们// 为了安全起见,这里应该添加额外的逻辑来确保 $name 只包含允许的字符和格式// 例如,您可以使用 pathinfo 来获取文件的扩展名,并检查它是否在您的白名单中// 但由于我们坚持使用黑名单方法(尽管不推荐),我们将跳过这一步// 检查文件是否成功上传if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true;} else {// 上传失败,显示错误消息// 注意:在实际应用中,应该避免使用 JavaScript alert 来显示错误消息,因为这可能会暴露敏感信息// 更好的做法是在服务器端记录错误,并向用户显示一个通用的错误页面echo "<script>alert('文件上传失败,请重试。');</script>";}
}
?>
将敏感后缀字符替换成空格,那么以上的利⽤不能绕过了。但是这⾥忽略了运⾏
环境的因素,在windows下⼤⼩写通⽤,所以可以上传PHP后缀进⾏绕过。
php"><?php
// 定义允许上传的文件扩展名白名单
$whitelist = array("jpg", "jpeg", "png", "gif");// 假设 UPLOAD_PATH 是一个已定义的常量,指向安全的上传目录
define('UPLOAD_PATH', '/path/to/upload/directory/');// 确保上传目录存在且可写
if (!is_dir(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)) {die('上传目录不存在或不可写。');
}// 检查是否提交了上传表单
if (!empty($_POST['submit'])) {// 获取上传文件的原始名称$originalName = basename($_FILES['file']['name']);// 获取文件的扩展名(不区分大小写)$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));// 检查文件的扩展名是否在白名单中if (in_array($ext, $whitelist)) {// 生成一个新的文件名(避免使用原始文件名,以防止安全问题)$newName = uniqid() . '.' . $ext;// 检查文件是否成功上传if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $newName)) {$is_upload = true;echo "<script>alert('文件上传成功!');</script>";} else {// 上传失败,显示错误消息echo "<script>alert('文件上传失败,请重试。');</script>";}} else {// 文件扩展名不在白名单中,显示错误消息echo "<script>alert('不允许的文件类型。');</script>";}
} else {// 没有提交上传表单,可能显示一个上传表单echo '<form action="" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" name="submit" value="上传"></form>';
}
?>
NTFS Tricks
这个技巧对于⼀些,限制⽐较严格的⿊名单检测,特别有效。
php"><?php
// 定义上传路径常量(请确保这是一个安全的路径)
define('UPLOAD_PATH', '/path/to/your/upload/directory/');// 确保上传目录存在且可写
if (!is_dir(UPLOAD_PATH) || !is_writable(UPLOAD_PATH)) {die('上传目录不存在或不可写。');
}// 检查是否提交了文件上传表单
if (!empty($_FILES['file']) && $_SERVER['REQUEST_METHOD'] === 'POST') {// 获取上传文件的原始名称$originalName = basename($_FILES['file']['name']);// 获取文件的扩展名(转换为小写)$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));// 定义黑名单数组$blacklist = array("php", "php5", "php4", "php3", "phtml", "pht", "jsp", "jspa", "jspx", "jsw", "jsv", "jspf", "jtml", "asp", "aspx", "asa", "asax", "ascx", "ashx", "asmx", "cer", "swf", "htaccess", "ini");// 如果文件扩展名不在黑名单中if (!in_array($ext, $blacklist)) {// 为了安全起见,生成一个新的文件名(避免使用原始文件名)$name = uniqid() . '.' . $ext;// 尝试移动上传的文件到指定目录if (move_uploaded_file($_FILES['file']['tmp_name'], UPLOAD_PATH . $name)) {$is_upload = true; // 文件上传成功标志(虽然在这个脚本中没有被进一步使用)echo "<script>alert('文件上传成功!');</script>"; // 显示成功消息(注意:在实际应用中,应避免在客户端显示敏感信息)} else {// 上传失败,显示错误消息echo "<script>alert('文件上传失败,请重试。');</script>"; // 显示失败消息(同样,应避免在客户端显示敏感信息)}} else {// 文件扩展名在黑名单中,显示错误消息echo "<script>alert('不允许的文件类型。');</script>"; // 显示黑名单错误消息(同样,应避免在客户端显示敏感信息)}
} else {// 没有提交文件上传表单或请求方法不是POST// 这里可以显示一个文件上传表单或其他内容echo '<form action="" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit" value="上传"></form>';
}
?>