PS:日常刷EDUSRC思路~~~~ 欢迎各位大佬指点不足
废话:
因为之前发过了一篇文章:实战渗透-从敏感文件泄露到Getshell
内容是通过扫描备份文件得到文件源代码进行代码审计,所以自己打算写一个专门扫描此类信息的工具。
主要是以异步的方式去实现批量扫描指定目录或文件,不过功能比较单一,是以响应头里面的Conten—Type做为判断。主要是为了速度。。毕竟用的aiohttp,aiomysql
如:
RAR文件: Content-Type: application/octet-stream ZIP文件: Content-Type: application/x-zip-compressed
目前还没有写完。。部分功能均已实现.预计下周会开源到github上,欢迎各位体验。
同时,队里的Kemoon师傅也写了一个针对信息泄露的工具 (.svn,.git,.rar,.zip)
github地址:https://github.com/lakemoon602/snail2.0
正文:
0x01:
在测试程序的期间,对部分网站进行了一些备份文件扫描:
扫描规则如下:
asds.XXXXXXX.com / asds.zip || asds.rar abc.XXXXXXX.com / abc.zip || abc.rar test.asds.XXXXXXX.com / test.zip || test.rar
在其中一个站点得到了文件备份。具体格式如下:
是一个监测平台的源代码程序,分析了内容。具体信息如下:
中间件: Tomcat 开发框架: Spring MVC 数据库: Oracle 默认账户密码: System ****2020
在web.xml中可以清楚的看到
程序对一些访问地址进行了登录过滤,除了一些images和scrpits,css目录可以访问,其他目录都会重定向/login.jsp
在web.xml中没有发现其它存在敏感操作的Servlet地址,所以,当下最重要的事情是解决未登录的问题。
由于之前在配置文件中发现了默认账户密码的信息,那么还是老方法。
收集同系统站点-挨个挨个测。
fofa一条龙服务~
挨个挨个站点的测试,最终在第五页测出了一个没有修改默认密码的站点。(总体看来默认密码的用处不会太大了)
权限为系统管理员。。还是蛮大的。
这里解决了未登录的问题,那么接下来就可以想办法getshell了。
由于是MVC模式,直接在代码层中找到Controller控制器即可。这里直接全局搜索一波upload
还挺多的,开始一个一个分析。
由于是Spirng MVC,路由地址会定义在方法上
如:
@RequestMapping(method = {RequestMethod.POST}, value = {"/test/test}) 注册路由地址为/test/test ,且只接受POST请求
随意点开一处包含upload关键词的方法,可以看到60行进行了白名单验证:
只允许:docx,pdf,doc,xls,xlsx 后辍的文件上传
这里的验证是直接写在方法体中的,所以可以明显看出效验的操作。
如果效验是以单独的方法处理的,使用JD可以快速追踪对应方法,进行查看。
最终在某处发现了未效验的文件上传操作:
主要代码如下:
String type = matUrl.getOriginalFilename().substring(matUrl.getOriginalFilename().lastIndexOf(".")); String OriginalFilename = matUrl.getOriginalFilename().substring(0, matUrl.getOriginalFilename().lastIndexOf(".")); String saveName = System.currentTimeMillis() + type; String saveName2 = OriginalFilename + type; String contextPath = request.getSession().getServletContext().getRealPath("/upload"); File savefile = new File(contextPath + "/" + saveName); File matsupportFile = new File(contextPath); if (!matsupportFile.exists()) matsupportFile.mkdirs(); if (!savefile.exists()) savefile.createNewFile();
其中type是获取文件后辍的
String type = matUrl.getOriginalFilename().substring(matUrl.getOriginalFilename().lastIndexOf("."));
OriginalFilename 为上传文件名
String OriginalFilename = matUrl.getOriginalFilename().substring(0, matUrl.getOriginalFilename().lastIndexOf("."));
那么如果会进行文件效验,可以是跟type进行对比。直接追踪方法在哪里使用了type变量即可
最终调用的两处都是字符拼接,saveName,saveName2.并没有进行任何效验操作。
接着往下走
这里指定了文件存储目录为upload
String contextPath = request.getSession().getServletContext().getRealPath("/upload");
后面的就是存储文件的代码了
File savefile = new File(contextPath + "/" + saveName);//最终文件存储名 File matsupportFile = new File(contextPath);//通过将给定路径名字符串转换成抽象路径名来创建一个新 File 实例。 if (!matsupportFile.exists()) //如果目录不存在,则创建 matsupportFile.mkdirs(); if (!savefile.exists())//如果文件不存在,则创建 savefile.createNewFile(); matUrl.transferTo(savefile);
这里要注意: 开始的时候定义了两个变量名: saveName和saveName2.最终存储的是saveName
String saveName = System.currentTimeMillis() + type; System.currentTimeMillis() == new Date()
saveName是时间转换后的内容+文件后辍
OK,这样下来这里肯定是可以直接getshell的。还是老样子。构造POC:
<form action="地址" method="post" enctype="multipart/form-data" > <input type="file" name="Filedata"/> <input type="submit" value="提交"/> </form>
由于过滤器的问题,这里是需要替换下已登录账户的Cookie的。
上传JSP代码如下:
<%String name = "yuanhai";%> <h1>hello<%=name%></h1>
得到文件地址 并访问: 注意(web.xml中并未设置upload无需登录,所以访问upload下的文件也需要登录。如果是上传一句话,别忘记设置Cookie)
成功执行代码~
0x02
由于这个比较鸡肋。所有敏感操作都需要登录才行。。看着fofa搜索结果足足有79条。。。
79个站点=3x6(18)+76x3(228) = 246分,卧槽。一波246分,这谁经得住诱惑。必须干穿他!
交一个站点 = 6分, 交79个站点 = 246分
于是继续审计:
由于web.xml的登录过滤,搞得十分憋屈,只能死马当活马医。心里想:"会不会有/image/或者/scripts/开头的路由地址?"
虽然这个想法不太现实。但也不是没有可能。
全局搜索了下 image , script , css 等web.xml没有限制的路径。
不得不说运气十分的好!!!!!!!!
在SSOcontroller下面发现一处以/CSS开头的路由地址
SSO???
单点登录(SingleSignOn,SSO),就是通过用户的一次性鉴别登录。当用户在身份认证服务器上登录一次以后,即可获得访问单点登录系统中其他关联系统和应用软件的权限,同时这种实现是不需要管理员对用户的登录状态或其他信息进行修改的,这意味着在多个应用系统中,用户只需一次登录就可以访问所有相互信任的应用系统。这种方式减少了由登录产生的时间消耗,辅助了用户管理,是目前比较流行的 简单的说,就是常见的统一身份认证系统。这个在每个高校基本都有一个。目前主流SSO有,金智,联亦,正方。等等
一般来说,SSO是通过ticket来进行身份效验,这个一般可以逆向解码出来。
但是看了下这个认证逻辑:
挨个分析:
先看第一层if
获取请求头里的Referer String referer = request.getHeader("REFERER"); if (this.urlIP == null) //如果urlip为null。 this.urlIP = SysDataCache.getParamValue("urlIP"); if (this.urlDomain == null) //urlIP,urlDomain 这个无法在请求头里定义。是从系统配置里获取 this.urlDomain = SysDataCache.getParamValue("urlDomain");
第二次if
if (referer != null) { //如果referer不为空。那么执行下面的 if ((this.urlIP.length() > 0 || this.urlDomain.length() > 0) && referer.indexOf(this.urlIP) != 0 && referer.indexOf(this.urlDomain) != 0) { System.out.println("REFERER:" + referer); System.out.println("urlIP:" + this.urlIP); System.out.println("urlDomain:" + this.urlDomain); request.setAttribute("msg", "); request.getRequestDispatcher("/error.jsp").forward((ServletRequest)request, (ServletResponse)response); return mav; } } else { //当referer为空时。跳转到/error.jsp System.out.println("REFERER:" + referer); System.out.println("urlIP:" + this.urlIP); System.out.println("urlDomain:" + this.urlDomain); request.setAttribute("msg", "); request.getRequestDispatcher("/error.jsp").forward((ServletRequest)request, (ServletResponse)response); return mav; }
主要的是
if ((this.urlIP.length() > 0 || this.urlDomain.length() > 0) && referer.indexOf(this.urlIP) != 0 && referer.indexOf(this.urlDomain) != 0)
这里是判断urlIP和urlDomain的长度的。这个我们没法定义,是从系统配置里获取。可以不用管。只需要在请求头里添加属性referer就可以。 那么这层逻辑满足。
往下走。
if ("admin".equals(xxx)) { //xxx为接受参数。这里把接受参数和admin进行对比。也就是说不能登录admin账户 request.setAttribute("msg", "); request.getRequestDispatcher("/error.jsp").forward((ServletRequest)request, (ServletResponse)response); return mav; } System.out.print("+ xxx); User dbUser = this.userService.getUserByUserID(xxx); if (dbUser == null) { mav.addObject("msg", "+ xxx + ")); mav.setViewName("/error"); } else if (dbUser.getLocked().intValue() == 1) { mav.addObject("msg", "+ xxx + ")); mav.setViewName("/error"); } else { mav = this.userLoginController.login(request, dbUser, "1"); Map tmp = mav.getModel(); String forwardURL = (tmp.get("forwardURL") == null) ? "" : tmp.get("forwardURL").toString(); System.out.print(forwardURL); return new ModelAndView("forward:" + forwardURL + ""); }
主要的是
User dbUser = this.userService.getUserByUserID(xxx);
这里的getUserByUserID。看一下后端是根据什么查询。
这里看了下 userid 就是账户名:
也就是说:
User dbUser = this.userService.getUserByUserID(xxx); if (dbUser == null) { mav.addObject("msg", "+ xxx + ")); mav.setViewName("/error"); } else if (dbUser.getLocked().intValue() == 1) { mav.addObject("msg", "+ xxx + ")); mav.setViewName("/error"); } else {
当传入账户存在,且不为admin,且不被锁定。那么可以直接登录。
下面这段代码直接证实了猜想。
} else { mav = this.userLoginController.login(request, dbUser, "1"); Map tmp = mav.getModel(); String forwardURL = (tmp.get("forwardURL") == null) ? "" : tmp.get("forwardURL").toString(); System.out.print(forwardURL); return new ModelAndView("forward:" + forwardURL + ""); }
调用了userLoginController.login 直接传入dbUser。执行登录操作。
那么实践
这里可以直接爆破用户
成功登录
那么集合之前的文件上传。。。。。美滋滋