某友,不(敢)多说。本文对前段时间热传的文件上传漏洞的成因做简单的分析(要是没看过,当我没说)。
常规操作 把源码导入idea 将对应lib下的jar文件添加为库
public class LoginInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory.getLogger(LoginInterceptor.class);
private static final String[] IGNORE_URI = new String[]{"/initcfg", "/login", "/index", "/automsg", "/loginxietong", "/loginportal", "/signportal", "/loginportaldetail", "/loginportalmobile"};
public LoginInterceptor() {
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取当前会话的 HttpSession 对象
HttpSession session = request.getSession();
// 获取名为 "user_logined" 的属性值,用于判断用户是否已登录
Object logined = session.getAttribute("user_logined");
// 获取当前请求的 URL 地址
String url = request.getRequestURL().toString();
// 如果当前请求的 URL 不以 "mp/" 或 "mp" 结尾,即不是请求登录页或首页,则执行下面的逻辑
if (!url.endsWith("mp/") && !url.endsWith("mp")) {
// 如果当前请求的 URL 是以 "index.html" 结尾的,则执行以下逻辑
if (url.endsWith("index.html")) {
// 如果用户未登录,则重定向到登录页,并返回 false
if (logined == null) {
response.sendRedirect(request.getContextPath() + "/login.html");
return false;
} else {
// 如果用户已登录,则返回 true,允许请求继续执行
return true;
}
} else if (!(handler instanceof HandlerMethod)) {
// 如果处理器不是 HandlerMethod 类型的实例(即不是 Controller 方法),则直接返回 true,允许请求继续执行
return true;
} else {
// 否则,即为普通请求,则执行以下逻辑
HandlerMethod handler2 = (HandlerMethod)handler;
// 获取处理器方法上的 ResponseBody 注解
ResponseBody json = (ResponseBody)handler2.getMethodAnnotation(ResponseBody.class);
String msg = "";
boolean flag = false;
// 记录请求的 URL 地址
logger.info(">>>: " + url);
// 遍历忽略的 URI 列表,判断当前请求的 URL 是否包含在忽略列表中
for (String s : IGNORE_URI) {
if (url.contains(s)) {
flag = true;
break;
}
}
// 如果当前请求的 URL 不在忽略列表中,则执行以下逻辑
if (!flag) {
// 进行自动登录,并返回相关提示信息
msg = this.autoLogin(request);
if (logined == null) {
// 如果用户未登录,则返回错误信息或进行重定向
msg = StringUtils.isNotEmpty(msg) ? msg : "<a href='/mp/logout' class='tips_login'>您未登录或长时间未操作,请先登录<a/>";
if (null == json) {
if (url.indexOf("indexforportal") >= 0) {
response.sendRedirect("/mp?indexforportallogin=portallogin");
} else {
response.sendRedirect("/mp");
}
} else {
MPUtils.sendResponseMsg(response, false, msg);
}
} else {
// 如果用户已登录,则设置 flag 为 true
flag = true;
}
}
// 根据 flag 的值决定是否允许请求继续执行
return flag;
}
} else if (logined == null) {
// 如果当前请求的 URL 以 "mp/" 或 "mp" 结尾,并且用户未登录,则重定向到登录页,并返回 false
response.sendRedirect(request.getContextPath() + "/login.html");
return false;
} else {
// 如果当前请求的 URL 以 "mp/" 或 "mp" 结尾,并且用户已登录,则返回 true,允许请求继续执行
return true;
}
}
到这里就简单明了了,只需要让URL包含IGNORE_URI中的任意URL即可绕过权限认证。就是这么简单(bushi)!
再让我们回到spring-mvc.xml
OK,获得Controller控制的位置,com.yonyou.uap.mp,那就直接跳过去,收获了若干Controller控制器!Easy(zhende)!
// 将该类声明为Spring MVC控制器
// 指定该控制器处理请求的基础URL路径为"/uploadControl"
"/uploadControl"}) ({
// 定义名为UploadFileServlet的Servlet类,继承自HttpServlet类
public class UploadFileServlet extends HttpServlet {
// 序列化版本UID
private static final long serialVersionUID = 1L;
// 创建Logger对象用于记录日志
private static Logger logger = LoggerFactory.getLogger(UploadFileServlet.class);
// 自动注入IMSGService对象
// 用于处理消息的服务接口
private IMSGService msgService;
public UploadFileServlet() {
}
// 声明处理请求的方法
(
// 指定处理请求的URL路径为"/uploadControl/uploadFile"
value = {"uploadFile"},
// 限定请求方法为POST
method = {RequestMethod.POST}
)
protected void doPost(HttpServletRequest request, HttpServletResponse response, String pluginCode, String pk_message) throws ServletException, IOException {
// 设置响应字符编码为UTF-8
response.setCharacterEncoding("UTF-8");
// 设置响应内容类型为HTML,字符编码为UTF-8
response.setContentType("text/html;charset=utf-8");
// 获取响应输出流
PrintWriter writer = response.getWriter();
// 设置请求字符编码为UTF-8
request.setCharacterEncoding("utf-8");
// 设置消息类型为TASK
String msgtype = "TASK";
// 初始化文件名为空字符串
String file = "";
// 初始化返回信息为空字符串
String returnInfo = "";
// 声明BufferedReader对象
BufferedReader bf = null;
// 检查请求是否是multipart类型的表单数据
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
// 如果是multipart类型的表单数据
if (isMultipart) {
// 创建DiskFileItemFactory对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 创建ServletFileUpload对象
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置请求头编码为UTF-8
upload.setHeaderEncoding("UTF-8");
try {
// 解析请求,获取表单项迭代器
Iterator items = upload.parseRequest(request).iterator();
// 遍历表单项
while(items.hasNext()) {
// 获取表单项
FileItem item = (FileItem)items.next();
// 如果不是普通表单项(即文件项)
if (!item.isFormField()) {
// 计算文件大小(MB)
long filesize_m = item.getSize() / 1024L / 1024L;
// 如果文件大小超过32MB
if (filesize_m > 32L) {
// 返回文件大小超限的错误信息
writer.println("{"forbidden":true,"msg":"文件大小不能超过32M!"}");
// 结束方法执行
return;
}
// 获取文件名
String fileName = item.getName();
// 截取文件名
fileName = fileName.substring(fileName.lastIndexOf("\") + 1);
// 获取应用程序的上下文路径
String contextPath = MPUtils.getContextRootPath(request.getSession().getServletContext());
// 设置文件上传目录
String staticUploadFileDir = contextPath + "uploadFileDir";
// 如果上传目录不存在
if (!(new File(staticUploadFileDir)).isDirectory()) {
// 创建上传目录
(new File(staticUploadFileDir)).mkdir();
}
// 构建文件路径
String path = staticUploadFileDir + File.separator + fileName;
// 创建文件对象
File uploaderFile = new File(path);
// 写入文件
item.write(uploaderFile);
// 调用消息服务接口上传文件
this.msgService.uploadFile(pluginCode, msgtype, pk_message, fileName, uploaderFile);
// 删除上传的临时文件
uploaderFile.delete();
}
}
// 返回文件上传成功的信息
writer.println("{"forbidden":false}");
} catch (Exception var25) { // 捕获异常
// 打印异常堆栈信息
var25.printStackTrace();
// 返回异常信息
writer.println("{"forbidden":true,"msg":"" + var25.getMessage() + ""}");
} finally {
// 关闭输出流
writer.close();
}
}
}
}
平平无奇,毫无新意,敢敢单单!
既然没有执行uploaderFile.delete(); 还产生了报错,那问题就一定是出在了this.msgService.uploadFile(pluginCode, msgtype, pk_message, fileName, uploaderFile);
废话不多说!上代码
public String uploadFile(String pluginCode, String msgtype, String pk_message, String fileName, File file) {
// 定义一个 SourceSystemVO 类型的变量 systemvo,并初始化为 null
SourceSystemVO systemvo = null;
// 定义一个 String 类型的变量 ret,并初始化为空字符串
String ret = "";
// 检查 isUserDB 的值是否为 false
if (!this.isUserDB) {
// 如果 isUserDB 为 false,从 XML 中获取 systemvo 对象
systemvo = this.sourceSystemServiceForXML.getSystemVOByCodeFromXML(pluginCode);
} else {
// 如果 isUserDB 为 true,从数据库中获取 systemvo 对象
systemvo = this.sourceSystemService.getSystemVOByCode(pluginCode);
}
// 根据 systemvo 获取对应的插件对象
IMessagePlugin plugin = this.getPluginByCode(systemvo);
// 检查插件对象是否不为 null
if (null != plugin) {
// 调用插件的 uploadAttachment 方法上传附件,并将返回值赋给 ret
ret = plugin.uploadAttachment(systemvo, msgtype, pk_message, fileName, file);
}
// 返回结果
return ret;
}
好家伙,破案了,原来pluginCode参数还是个必要参数,输入的数据包没设置pluginCode参数,导致无法生成systemvo,无奈跳出了之前try进入到catch中 ,文件传上去却删不掉,可惜可惜。
复现结束!
原文始发于微信公众号(悦海数安):【实战】某友文件上传漏洞分析
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论