网络程序爬虫编程 破解渗透
3.33K subscribers
610 photos
15 links
Download Telegram
为了方便对不同类型的点进行适配,抽象了一个Handler出来,然后在根据不同的类型实现具体的ClassVisitorHandler内容,Handler.java具体代码如下:
在Java EE中通过劫持javax.servlet.Servlet的service方法和javax.servlet.Filter类的doFilter方法不但可以获取到原始的HttpServletRequest和HttpServletResponse对象,还可以控制Servlet和Filter的程序执行逻辑。
可以将所有参数描述符为(Ljavax/servlet/http/HttpServletRequest;Ljavax/servlet/http/HttpServletResponse;)V的方法进行插入埋点,并缓存request、response对象
上面的代码将对所有实现javax.servlet.Servlet#service的方法进行了埋点处理(接口、抽象类除外),真正编译到jvm中的类如下:
可以看到,在对进入方法的时候调用了IAST中的方法cn.org.javaweb.iast.core.Http#enterHttp,在离开方法的时候,调用了cn.org.javaweb.iast.core.Http#leaveHttp其中enterHttp具体代码如下:
从上文中可以看到,传入的HttpServletRequest和HttpServletResponse对象存到了当前线程的上下文中,方便后续对数据的调取使用。
在Java EE中通过可以劫持获取输入源的所有方法,比如常用的getParameter、getHeader等类似的方法,在这里将对调用的方法、以及返回的参数进行跟踪,这里为真正污点跟踪的起点。可以简单的理解为就是http各个get方法即为来源,但这一结论不保证完全适配所有情况。对于Source相关的点处理的代码如下(示例代码为了便于理解未考虑异常处理):
只是简单的对于getParameter进行了埋点处理,让其调用IAST的处理逻辑,编译到JVM的Class内容如下:
可以看到,在进入方法后调用了cn.org.javaweb.iast.core.Source#enterSource,具体内容如图:
对参数、类名、方法名、描述符等信息添加到了callChain中. 在方法结束前获取了返回值,并且调用了cn.org.javaweb.iast.core.Source#leaveSource方法,将返回值传入了进去,那么在处理的时候,就将其结果放到了callChain.returnObject。
传播点的选择是非常关键的,传播点规则覆盖的越广得到的传播链路就会更清晰。比如简单粗暴的对String、Byte等类进行埋点,因为中间调用这些类的太多了,所以可能导致一个就是结果堆栈太长,不好对调用链进行分析,但是对于传播点的选择,可以更精细化一些去做选择,比如Base64的decode、encode也可以作为传播点进行埋点,以及执行命令的java.lang.Runtime#exec也是可以作为传播点的,因为最终执行命令是最底层在不同系统封装的调用执行命令JNI方法的类,如java.lang.UNIXProcess等,所以将java.lang.Runtime#exec作为传播点也是一个选择。为了方便演示污点传播的效果,对Base64的decode以及encode和java.lang.Runtime进行了埋点处理,具体实现代码如下(示例代码为了便于理解未考虑异常处理):
真正运行在JVM中的类如下:
java.util.Base64$Decoder#decode
java.lang.Runtime
可以看到其实也是在方法进入后和方法离开前插入了IAST的代码逻辑,以便直观的观察到入参值以及返回值发生的变化。
在成功发出未经身份验证的请求之前,我们还需要克服一个障碍,但这个障碍其实很小。每个页面请求/ecp都需要一个被称为“ECP canary”的票据。如果没有 canary,请求将返回 HTTP 500 错误。然而,攻击者仍然有机会,因为 500 错误响应会附带一个有效的 canary:
这种漏洞利用方式假定攻击者与受害者拥有同一台 Exchange 服务器上的帐户。它会安装一条转发规则,使攻击者能够读取受害者的所有收件。在某些 Exchange 环境中,管理员可能设置了全局配置值,允许转发规则指向任意 Internet 目标地址,在这种情况下,攻击者完全不需要任何 Exchange 凭据。此外,由于整个/ecp站点都可能受到影响,因此也可能存在其他多种利用方式。
Config 静态类是 ObjcectInputFilter 接口的一个内部静态类。
configuredFilter 是一个静态字段,所以调用 Config 类的时候就会触发 configuredFilter 字段的赋值。
可以看到会拿到 jdk.serailFilter 属性值,如果不为空,会返回 createFilter(var0)的结果(createFilter 实际返回的是一个 Global 对象)。
jdk.serailFilter 属性值获取的方法用两种,第一种是获取 JVM 的 jdk.serialFilter 属性,第二种通过在 %JAVA_HOME%\conf\security\java.security 文件中指定 jdk.serialFilter 来设置。另外从代码中可以看到,优先选择第一种。
Config#createFilter 则会进一步调用 Global.createFilter方法,这个方法在介绍 Global 类的时候会说,其实就是将传入的 JEP 290 规则字符串解析到Global对象的 filters 字段上,并且返回这个 Global 对象。
我们来看一下RegistryImpl_Stub.lookup对服务端返回的结果是怎么处理的,可以看见在 RegistryImpl_Stub.lookup 会直接对服务端返回的对象调用 in.readObject 方法,而 in 的 serialFilter在这里是为 null 的:
所以客户端在进行 RegistryStub.lookup 操作的时候会直接导致 RCE :
同理 RegistryStub.list 也是如此:
但是用上面的服务端恶意代码并不能触发 RCE ,因为上面服务端恶意代码是利用 Registry_skel 来写入对象的,可以看到写入的是一个字符串数组:
首先 Enumerate.connect(this.registry) 返回的实际上是 RegistryImpl_Stub 对象,底层调用的是 LocateRegistry.getRegistry 方法。
然后调用 this.registry.loadObjects(), this.list() 实际调用的是 RegistyImpl_Stub.list() 方法,得到注册中心的所有绑定的对象名:
接着会调用 this.loadObjects(names), 会调用 this.lookup(name) ,底层实际使用的是 RegistryImpl_Stub.lookup() 方法,上面分析过 RegistryImpl_Stub.lookup 会直接反序列化服务端传过来的恶意对象,并且 readObject时使用的ObjectInputStream 对象中的 serialFilter 是 null。