投稿卖钱的一篇文章,博客备份一下
前序:
一个被测过且加固了好多次的 App,后端某个接口存在 fastjson,jdk6+tocmat6极品上古环境,看我如何前台shell
开始
开局给了一个套了二代壳的APK文件:
笔者在测试App的时候,通常会先解压看下Androidmanifest.xml文件,很多时候从这个文件中的一些Activity名字基本上就可以判断出他的很多业务逻辑与功能,这就相当于一个web系统,他告诉了你他有哪些页面一样
大多时候App 中的 Activity 多为导出状态,可通过 Objection 实现越权访问,可以帮助我们快速判断了解其业务功能与大致存在的接口信息:
通过对导出 Activity 的越权,我们成功访问到了其应用内部的一处接口,该接口存在客户端侧的加密调用逻辑(加吧,正好不用绕waf了):
在这种场景下,可以借助Frida先跑一些通用的自吐加密算法,具体可以参考下我之前整理的:https://github.com/wa1ki0g/Script-For-FridaHook/tree/main/自吐算法
不过这个 App 使用了一些自定义的加密函数,脚本跑出来的结果不太准确。另外由于它是一个二代壳,脱壳难度不算大,所以我这里选择先进行脱壳,再进一步分析。
二代壳借助fart hook内存可以直接脱掉:
脱壳后发现其加密逻辑使用 AES,密钥由静态密钥拼接salt 构成。salt 经过混淆处理,加密处的代码逻辑为:
1 | public static String encryptWithSalt(String plaintext) { |
这里可以直接通过frida hook下这个函数,打印下值就能拿到了,hook.js:
1 | Java.perform(function () { |
随便访问一个查询的接口,并抓下包,解密一下看看传过去的是个什么东西:
这里习惯给参数加上了单双引号,加密发送,发现报错信息syntax error, position at 0, name loginname
第一反应 fastjson,syntax error, position at 这个报错见的太多了,于是尝试确定。dnslog探测,
InetAddress Inet4Address Inet6Address URL 都试了,dns均没有收到。尝试通过报错直接显示版本,目标报错信息显示太少这里也没有直接显示出来:
1 | { |
但是fuzzpayload的时候,发现JdbcRowSetImpl打ssrf延迟可以成功:
1 | {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true} |
这回不仅可以确定fastjson,还可以确定版本了,@type:com.sun.rowset.JdbcRowSetImpl 直接能打,版本不会超过1.2.24的样子。ok那问题就变成了fastjson不出网。
先回顾下相关知识
fastjson连DNS都不出的情况下,像JNDI 、JDBC 这种需要出网的链基本都打不了,能利用的主要有三种方式:
- TemplatesImpl链
- DataSource+Bcel
- C3P0二次反序列化
先说第一个,TemplatesImpl,老生常谈的一个东西了,在很多gadget中最后rce的方式都是使用他去加载字节码:
1 | TemplatesImpl#newTransformer() →......→ TransletClassLoader#defineClass() |
而在fastjson中,因为_outputProperties属性的getter是直接调用了newTransformer()方法,所以可以直接使用TemplatesImpl去进行攻击:
但是这条链在fastjson中有个致命的缺陷,即TemplatesImpl一些会用到的属性几乎都是不存在setter的私有属性,所以就导致在不开启 Feature.SupportNonPublicField
(是否支持访问和赋值类中的非 public 字段)的情况下是利用不了的,而且这玩意也不是默认开的,所以就导致实战中这个链几乎打不通的,当然这个目标也不例外。
pass掉第一种,再来简单说下第二个:DataSource+Bcel
rce的点是通过BasicDataSource类的一个getter即getConnection方法,会一直走到createConnectionFactory方法,然后根据我们传过去的loader名即com.sun.org.apache.bcel.internal.util.ClassLoader去获取该类并实例化该类
最终在loadclass中将BCEL字符串解码得到字节码并进行加载:
看完上面流程我们大致了解了,通过
1 | org.apache.tomcat.dbcp.dbcp.BasicDataSource |
就可以进行rce,com.sun.org.apache.bcel.internal.util.ClassLoader是jdk中自带的(限制版本为8u251之前),org.apache.tomcat.dbcp.dbcp.BasicDataSource又是tomcat中自带的,
1 | tomcat6、7对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource |
那么是不是像一些文章中说的那样,在fastjson版本适合的情况下jdk版本也在8u251前就可以畅通无阻了呢?
并不是,我们拿此次的tomcat6的目标以及poc来看:
1 | { |
首先是要调用driverClassLoader与driverClassName的setter来对private私有变量进行赋值:
而在tomcat6中虽然也自带了org.apache.tomcat.dbcp.dbcp.BasicDataSource,但是可以发现其是没有driverClassLoader的setter方法,甚至是没有driverClassLoader这个变量的:
ok,那么最终我们的第二种方法也不行,还剩下了一种,同时也是在fastjson不出网中利用最广泛的一种,c3p0的二次反序列化。
其实关于关于C3P0的攻击方式主要也有三种,但是在不出网的情况下能用的也只有3:
1、利用URLClassLoader进行远程类加载
2、JNDI注入
3、HEX反序列化
简单说下他的原理,在fastjson中的触发点为userOverridesAsString属性的setter:
最终在C3P0ImplUtils#parseUserOverridesAsString 中处理16进制字符串并开始反序列化逻辑:
ok,那么现在问题就可以看成目标存在一个原生的反序列化漏洞,并且fastjson本身也是存在原生gadget的我们先利Thread.sleep尝试下延时,看看能不能执行代码成功:
1 | import com.alibaba.fastjson.JSONArray; |
1 | import com.alibaba.fastjson.JSON; |
很遗憾,这里生成的poc并没有延时成功,难道连c3p0都没有?于是对拿出了自己之前整理过的依赖字典简单改了下并对目标进行fuzz,主要fuzz一些反序列化的依赖与JDK的特征类:
1 | import org.example.AESUtil; |
可以看下我们执行的结果,可以发现是存在C3p0的:
这里接着往下看可以发现fuzz出了目标是JDK6:
这里先简单介绍下那个fastjson1的gadget,如果分析过fastjson那条原生gadget可以知道:
1 | 通过BadAttributeValueExpException#readObject去触发toString,然后调用toJSONString,再调用getter,实现反序列化利用。 |
Ok,我们debug下jdk6的代码,可以看到jdk6中的BadAttributeValueExpException,并没有重写readObject方法,甚至他的父类和祖父类都没有重写:
ok,原因找到了。对于这里可以另外找几个会触发toString的,这就又变成了一个经典的CTF问题,toString被黑名单,这里感兴趣的师傅可以去看下另外的文章,但是在本环境中几乎没有行的通的。
这里可以说个题外的东西:我们再使用某些版本的idea去debug模式下调试gadget的时候,调试器显示 xx 的内容的时候,可能会去调用 toString(),导致提前执行gadget,比如这条fastjson的gadget,感兴趣的师傅可以去试下。
其实看到里面有cc的依赖,我这里最开始也尝试了利用cc1+TemplatesImpl+cp30进行Thread.sleep,但是也没有成功,debug下发现了个好玩的问题,比如当我们使用如下代码也就是利用javassist去生成恶意字节码时,网上的各种漏洞的利用工具几乎都是通过javassist去生成字节码的,示例代码:
1 | ClassPool pool = ClassPool.getDefault(); |
可以看到他这里对当前的MAJOR_VERSION通过一些特征进行了判断,并生成相应版本的字节码:
Java 8 编译代码,默认会生成 major_version = 52 的字节码,而 Java 6 只支持到 major_version = 50。因此,Java 6 JVM 加载该 class 文件时,会因版本过报错。而TemplatesImpl就是通过加载class去执行代码的,这里我们可以通过setMajorVersion方法去修改版本,或者使用对应JDK生成就好了。那么最后一个bug找到了,使用cc1+TemplatesImpl打个延时试下,加密发送paylpad,发现延迟成功:
ok,可以执行代码了,尝试注内存马拿shell,这里目标是tomcat6的环境,而之前就有师傅发过了相关文章,可以通过遍历线程的方式去获得tomcat6的上下文,大致逻辑为:
1 | 通过如下方式遍历线程: |
但是在本地测试时很不稳定,并且目标也没有成功,猜测是可能HTTP 请求处理线程已经处理完了,但是恶意字码还没有执行完,导致当前遍历线程时得到的结果为NULL,所以这里根据一些师傅放出的代码和文章改了一版能稳定利用的.java的代码,注入器代码:
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
注入的是蚁剑filter内存马,其代码逻辑:
1 | import javax.servlet.*; |
ok,我们最终利用c3p0+cc1+TemplatesImpl去注入内存马:
1 | package fast.web.src; |
最终poc:
1 | import com.alibaba.fastjson.JSON; |
最终一发入魂: