文件上传代码分析

server/2024/11/27 9:03:09/

目录

  • 不同类型的语言
    • 脚本语⾔/解释型语⾔
    • ⼀次编译到处运⾏
    • 编译型语⾔
  • 不同语⾔的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>';
}
?>

此时,如果在windwos下可以上传shell.php::$DATA即可绕过。最终shell为shell.php


http://www.ppmy.cn/server/145300.html

相关文章

Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)

14.1 命令请求的执行过程 一个命令请求从发送到获得回复的过程中&#xff0c;客户端和服务器都需要完成一系列操作。 14.1.1 发送命令请求 当用户在客户端中输入一个命令请求的时候&#xff0c;客户端会把这个命令请求转换为协议格式&#xff0c;然后通过连接到服务器的套接字…

大数据新视界 -- 大数据大厂之 Hive 数据桶:优化聚合查询的有效手段(下)(10/ 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

刷题日常(移动零,盛最多水的容器,三数之和,无重复字符的最长子串)

移动零 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 俩种情况&#xff1a; 1.当nums[i]为0的时候 直接i 2.当nums[i]不为0的时候 此时 …

Java面试之多线程并发篇

前言 本来想着给自己放松一下&#xff0c;刷刷博客&#xff0c;突然被几道面试题难倒&#xff01;说一说自己对于 synchronized 关键字的了解&#xff1f;说说自己是怎么使用 synchronized 关键字&#xff1f;什么是线程安全&#xff1f;Vector是一个线程安全类吗&#xff1f;…

《基于FPGA的便携式PWM方波信号发生器》论文分析(三)——数码管稳定显示与系统调试

一、论文概述 基于FPGA的便携式PWM方波信号发生器是一篇由任青颖、庹忠曜、黄洵桢、李智禺和张贤宇 等人发表的一篇期刊论文。该论文主要研究了一种新型的信号发生器&#xff0c;旨在解决传统PWM信号发生器在移动设备信号调控中存在的精准度低和便携性差的问题 。其基于现场可编…

NeurIPS 2024 有效投稿达 15,671 篇,数据集版块内容丰富

NeurIPS&#xff0c;全称 Neural Information Processing Systems Conference&#xff0c;是神经信息处理系统的年度学术会议。该会议始于 1987 年&#xff0c;当时名为 NIPS。随着人工智能领域的快速发展&#xff0c;其影响力逐渐扩大&#xff0c;被越来越多的研究者和企业关注…

解决Flink读取kafka主题数据无报错无数据打印的重大发现(问题已解决)

亦菲、彦祖们&#xff0c;今天使用idea开发的时候&#xff0c;运行flink程序&#xff08;读取kafka主题数据&#xff09;的时候&#xff0c;发现操作台什么数据都没有只有满屏红色日志输出&#xff0c;关键干嘛&#xff1f;一点报错都没有&#xff0c;一开始我觉得应该执行程序…

PAT甲级-1134 Vertex Cover

题目 题目大意 给定一个图&#xff0c;n是定点数&#xff0c;m是边数&#xff0c;给出每条边的两个顶点来表示边。又给定k个顶点集&#xff0c;要求判断这些顶点集是否是定点覆盖集。是的话输出Yes&#xff0c;否则输出No。 思路 vertex cover是顶点覆盖的意思&#xff0c;即…