目录
不同类型的语言
脚本语⾔/解释型语⾔
代表就是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
// 设置内容类型和字符编码
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
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>
<?php
if ($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
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
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'])) {
// 检查文件大小是否为0
if (!$_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
// ...(之前的代码,如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
// 假设 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
// 定义允许上传的文件扩展名白名单
$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
// 定义上传路径常量(请确保这是一个安全的路径)
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>';
}
?>
此时,如果在windwos下可以上传shell.php::$DATA即可绕过。最终shell为shell.php。