lightless blog - 安全研究
type: channel
link: https://www.lightless.me/category/SecurityResearch
description: 安全研究
items: [CVE-2023-46604/CNVD-2023-80853] ActiveMQ < 5.18.3 RCE
items: SignMe 签名插件
items: BurpAuthTest 越权检测插件
items: Analyze ApacheCommonsText With Joern
items: Code Analysis With Joern
items: Apache2+Perl 造成的 CRLF 注入问题
items: XStream RCE Analysis
items: 新晋毒瘤ApacheShiro？
items: Padding Oracle Attack 浅析
items: XSS With Service Worker

[CVE-2023-46604/CNVD-2023-80853] ActiveMQ < 5.18.3 RCE
type: item
date: 2023-10-30T03:07:00+00:00
description: 补充：好像已经有 CNVD 编号了：CNVD-2023-80853 CVE：CVE-2023-46604 从 Github 上的 commit 记录里，很容易找到那个疑似修补漏洞的 commit：https://github.com/apache/activemq/pull/1098/commits 经过简单的比对，发现新代码对 Throwable 类型进行了验证，保证只有 Throwable 类型的变量到达某个函数。我们直接拉源码看一看： /// org.apache.activemq.openwire.v12.BaseDataStreamMarshaller#createThrowable private Throwable createThrowable(String className, String message) { try { Class clazz = Class.forName(className, false, BaseDataStreamMarshaller.class.getClassLoader()); Constructor constructor = clazz.getConstructor(new Class[] {String.class}); return (Throwable)constructor.newInstance(new Object[] {message}); } catch (Throwable e) { return new Throwable(className + ": " + message); } } 不难看出，这里直接实例化并执行了某个类的构造函数，但是这个构造函数必须含有一个单 String 类型的参数。我们反向追以下，看看哪里调用了 createThrowable 方法，尝试寻找下入口： createThrowable tightUnmarsalThrowable or looseUnmarsalThrowable in class <MessageAckMarshaller / ExceptionResponseMarshaller / ConnectionErrorMarshaller> 这个调用链并不复杂，利用 IDEA 的 FindUsage 功能，可以轻松的找到入口，是三个消息类型的反序列化函数入口。 中间有两个函数 tightUnmarsalThrowable 和 looseUnmarsalThrowable ，实际功能无差别，取决于 ActiveMQ 的某个设置项决定在实际运行时调用哪个函数进行反序列化。 wireFormat.tightEncodingEnabled If enabled, optimize for smaller encoding on the wire. This increases CPU usage. It is enabled by default. 原理搞清楚以后，只需要构造对应类型的 Message 并发送就可以了，以 ConnectionError 为例： /// org.apache.activemq.openwire.v12.ConnectionErrorMarshaller#tightUnmarshal /** * Un-marshal an object instance from the data input stream * * @param o the object to un-marshal * @param dataIn the data input stream to build the object from * @throws IOException */ public void tightUnmarshal(OpenWireFormat wireFormat, Object o, DataInput dataIn, BooleanStream bs) throws IOException { super.tightUnmarshal(wireFormat, o, dataIn, bs); ConnectionError info = (ConnectionError)o; info.setException((java.lang.Throwable) tightUnmarsalThrowable(wireFormat, dataIn, bs)); info.setConnectionId((org.apache.activemq.command.ConnectionId) tightUnmarsalNestedObject(wireFormat, dataIn, bs)); } 从代码…

SignMe 签名插件
type: item
date: 2023-07-10T08:25:00+00:00
description: 开源地址：https://github.com/lightless233/sign-me-2 0x00 上一篇文章主要分享了越权测试插件的思路，经过一段时间的使用，确实节省了不少时间，也遇到了新的问题。 很多网站现在发起请求时，会附加一个 sign 签名值，以保证参数值在传输的过程中不被篡改。这同样也给我们修改参数进行测试带来了一些麻烦。所以如果能在 burp 中修改完数据包后自动对参数进行签名，可以节省不少算签的时间（当然如果连签名算法都不知道，也没办法手动算签）。 0x01 基于以上的情况，简单糊了一个新的 burp 插件 SignMe，专门用于计算各类签名，并将计算好的签名替换回原始数据包。总体来说这个插件并没有什么特别的东西，毕竟只是一个简单的参数替换。 不过在这里动了一点小心思，并没有把某个特定的签名算法写死到插件里，二是额外塞了一个 JS 引擎进去，可以让用户通过编写 JS 代码自行实现签名算法。 由于 JS 中还需要和 Java 互相操作，最终选用了 graalvm 的方案。之所以使用了 JS 代码来编写签名算法，是因为在日常的测试过程中，大部分网站的签名算法都是由 JS 实现的，很多情况下我们甚至可以直接将网站的 JS 复制过来，稍微改一改就能复用了。如果选择其他语言，还需要经历一个翻译算法的过程，如果做过类似的工作就会发现翻译语言这个过程中会遇到很多的坑：比如不同语言加密库的实现不同、字符编码问题等等。所以干脆直接选择 JS 来作为签名算法的计算语言，既省时又省力。 在 sign-me 中，已经提前封装好了大量常用的方法，包括：打印日志、参数排序、计算常见哈希、获取时间戳等等，希望用户只是像拼积木一样就可以拼出一个签名算法，将更多的时间放到对目标的测试过程上，而不是将精力浪费在计算签名这种事情上。 当计算完成后，通过返回一个指定的数组，表示需要修改哪些参数，如下图所示： 在 burp 中，可以通过修改 sign-me 和 auth-test 的插件顺序，就可以实现自动对重放包进行签名的功能，从而进行半自动的越权测试了。 sign-me 的代码暂时也没有开源的计划，主要是因为写的太烂了（这是我写的第一个burp插件，所以写的很烂），计划在近期对代码进行一次重构，之后会放出来供大家一起使用。

BurpAuthTest 越权检测插件
type: item
date: 2023-06-13T06:35:29+00:00
description: 0x00 最近一段时间，做了一些自动化/半自动化的测试小工具，主要还是想从重复的工作中解放出来，把时间投入到更有意义的事情中。 开发语言也从之前的 Python 逐步切换到了 Rust，虽然 Python 是一门很棒的语言，可以让我快速的实现各种想法的原型，但是 Python 在工程化的时候，总会出现一些不尽人意的地方。恰巧之前也学过一些 Rust，便逐步将主力开发语言切换到了 Rust。关于这部分的内容，后续可以再单独讲讲。 在最近的一些测试工作中，很大一部分目标都是各类 CMS、WMS 系统，经过前期的一些人工测试，发现这类业务逻辑相对简单且以 CURD 为主的系统，很难出现一些高危的 Web 漏洞（特指常见高危Web漏洞，例如：命令执行、SSRF、SQL注入等）。主要的风险还是以水平越权和垂直越权为主。 很多人可能根据经验会有疑问，为什么很少甚至不会出现 SQL注入漏洞？ 在大型甲方企业的开发流程中，开发这类系统时，一般都不是从零开始写第一行代码的，而是使用该团队积累下来的各种二方包。对于常见的业务操作，例如 CURD、OSS上传等等，都有较为成熟的框架和解决方案。一旦出现过一次漏洞，通过对二方包的修复，很容易就实现了对所有其他系统的修复，以及在新系统中的预防。 顺便一提，对于其他类型的漏洞，例如 SSRF 和 命令注入等，在常规的 CURD 类的 CMS、WMS 系统中，根本不会出现相关的业务场景，所以更不会出现相关的漏洞了。 越权检测一直是扫描器领域长盛不衰的话题，目前在越权检测这个方向下面，还没有看到非常成熟的方案，也没有通用的银弹。 虽然没有银弹，但是一定有人已经造过轮子了，先去搜索了一些 Burp 上比较知名的越权测试插件，使用了一圈下来感觉都很不爽。有些界面丑陋且已经年久失修无法使用，有些逻辑复杂操作反人类，终于还是决定写一个自己用起来顺手的小插件。 0x01 在正式开始造轮子之前，我们先回顾一下手动测试越权的流程。 假设我们使用两个相同权限的账号 A 和 B，分别使用不同的浏览器登录。Chrome 登录账号 A，Edge 登录账号 B，给 Chrome 浏览器添加代理，把流量转发到 Burp。 在 Burp 中发现一个接口：http://api.example.com/user/get?id=10001 ，分析后发现是查看当前用户信息的接口。此时如果想测试越权，我们需要找到账号 B 中，此接口对应的参数，并将其复制过来到 Burp 中。假设 B 账号得到的参数为 id=51234 ，在 Burp 中修改参数后发送，并人工检查 Response 中是否含有 B 账号的数据。 在这个过程中需要来回切换多个窗口，复制粘贴不同账号的数据，最后还需要人工对比 Response 数据的内容，需要操作很多个步骤，并且非常的麻烦。另外这只是进行了两个同权账号的测试，如果系统的角色非常复杂，有管理员、普通会员、子账号的情况下，需要手工处理的工作就更多了。 测着测着，很自然的就出现了第二个想法：如果不替换参数，直接替换身份凭证是否可行？ 答案自然是可行的，我们不用再去复制 B 账号每个接口的参数，只需要登录一次 B 账号并获取到 B 账号的身份凭证信息（Cookie、X-ACCESS-TOKEN等），在每次测试 A 账号的时候，顺手把 A 账号的身份凭证修改为 B 账号，就相当于以 B 账号的身份使用 A 账号的参数发起请求，直接查看返回是否一致就可以了。我们不需要来回的切换窗口，完全在 Burp 一个窗口中操作就可以了，当 Response 过大不方便肉眼看的时候，甚至可以使用的 Burp 的 diff 功能检查两个包是否有相同数据。 当然这个方案也有弊端： 如果参数中、头中含有 CSRF Token 或类似的标记，此方法无法通过服务端的相关校验； 依然无法避免角色过多带来的重复操作问题，同一个接口，每一个角色都要替换数据发一次包； 在少部分业务场景下，如果参数本身是无规则字符串，例如 UUID、MD5 等情况且根本没有接口可以获取到其他账号的资源 ID，按照业务设计规则，可能不涉及越权问题； 这一点实际上是有分歧的，理论上来说，我使用其他账号的资源 ID 获取到了资源内容，本身就属于越权，只能最终与业务沟通并根据实际情况再做是否修复的决定。 到目前为止，我们有了一些进展，简化了替换参数的工作量。但是我仍然希望将这个过程自动化，在我每次测试其他漏洞的时候，Burp 自动化帮我替换身份凭证，并比对返回值是否相似。 0x02 我们首先要解决的问题，就是账号的替换的问题，对同一个接口的请求，要以何种策略进行重放，是这个插件的核心逻辑。 回顾一下正常的测试流程，为了测试水平越权和垂直越权，我们至少需要准备3个账号，如果我们只想测试水…

Analyze ApacheCommonsText With Joern
type: item
date: 2022-10-18T07:44:12+00:00
description: 近期 ApacheCommonsText 出现了 RCE 漏洞，CVE-2022-42889。刚好最近在用 Joern，想尝试下能否利用 Joern 发现这个 CVE，分析过程中遇到了不少的问题，记录一下。 1. 漏洞原理 关于原理这块，网上已经有很多的分析文章了，原理也比较简单，这里也不再赘述了。 简单来说，这个漏洞很像 log4j，也是替换字符串的功能出现了问题，在替换字符串的过程中调用了 org.apache.commons.text.lookup.StringLookup.lookup:java.lang.String(java.lang.String) 方法，根据实际内容调用了不同的 Lookup ，进而利用 ScriptStringLookup 导致了 RCE 漏洞。 2. 初次尝试 因为我们已经知道了漏洞触发点是 StringLookup.lookup 方法，现在我们想知道另外一个问题，除了网络上流传的 POC 中的 replace 方法外，是否还有其他的方法可以触发该漏洞？如果能发现其他的入口，不管是对编写白盒检查规则、渗透测试都有帮助。 sink比较好说，我们直接定义： def sink = cpg.method.fullName(".+\\.StringLookup.lookup.*") 对于 source 来说，由于本次分析目标是一个单独的库，我们姑且认为所有在 非 abstract、interface 内的 public 方法均可以被访问到，可以作为入口被其他的应用调用。 def source = cpg.method.where(_.isPublic).whereNot(_.isConstructor).whereNot(_.typeDecl.isAbstract).filter(_.parameter.size != 0) 接下来把他们串到一起，我们暂时先不使用 joern 自身的数据流分析功能，我们先从调用链上看看是否有更多的发现： joern> importCode("commons-text-1.9.jar", "commons-text") joern> sink.repeat(_.caller)(_.until(_.where(_.isPublic).whereNot(_.isConstructor).whereNot(_.typeDecl.isAbstract).filter(_.parameter.size != 0))).l 很不幸，这条查询并没有给出想要的结果，反而会持续卡死无响应。 3. 问题分析 起初，我以为是迭代查询次数太多，导致路径爆炸，进而导致 joern 迟迟无法结束查询。于是我尝试对 source 进行优化，发现无论怎么优化，最终符合条件的 source 数量都不少于 700 个。而且我无法干涉 joern 内置的 repeat 行为，想对其在迭代过程中进行剪枝也不太可能。 既然这条查询会卡死，那么我们一步一步来，看看每次迭代的结果，修改一下刚刚的查询语句，把路径打印出来： joern> def findStep(idx: Int) : Traversal[Vector[Long]] = { return cpg.method.fullName(".+\\.StringLookup.lookup.*").enablePathTracking.repeat(_.caller.dedup)(_.times(idx)).path.map( path => path.filter(n => n.isInstanceOf[Method]).map( n => { val nn = n.asInstanceOf[Method]; nn.id } ) ) } 直接定义一个函数，参数表示迭代多少次，并把路径上所有节点ID返回，我们开始查询吧： joern> findStep(1).l res483: List[Vector[Long]] = List( Vector(47685L, 24708L), Vector(47685L, 45583L) ) joern> findStep(2).l res484: List[Vector[Long]] = List( Vector(47685L, 24708L, 23750L) ) joern> findStep(3).l res485: List[Vector[Long]] = List( Vector(47685L, 24708L, 23750L, 23725L), Vector(47685L, 24708L, 23750L, 23750L) ) 当迭代到第三次的时候，出问题了，可以很明显的看到第二条链路上出现了递归，而 joern 内置…

Code Analysis With Joern
type: item
date: 2022-10-10T14:10:56+00:00
description: 0. 介绍 最近工作上做了一些渗透测试项目，在测试过程中发现有些系统使用了开源软件，通过对开源软件的代码审计，发现了该开源软件的漏洞，并利用该软件的漏洞拿下了目标。在整理完报告后，就一直在思考是否有一款工具，可以准确的告诉我： 代码中有哪些 Web 接口； 某个 Web 接口下面是否调用了某个函数； 某个具体的函数能从哪些 Web 接口访问到； 如果能做到这些，把代码的信息提取出来，可以让我像查询 DB 一样对代码进行查询，会极大的提高我在审计这方面的效率。除此之外，支持的语言越多越好，谁也不知道下一个目标是什么语言开发的。:) 而目前市面上大部分工具，都是针对发现漏洞制作的，无一例外都使用了污点传播的方法。这就导致了一个问题，如果我单纯的想获知代码中的一些信息而不是扫描漏洞，这些工具是无法使用的，因为这些工具的目的是发现漏洞，而不是汇总代码中的信息。虽然这些工具在扫描过程中也会整理这些信息（例如CFG等），但是也不会保存起来，即便保存了，也无法方便的查询。 经过了一番调研，已经打算自己造轮子了，甚至思路都已经想的差不多了，突然想起来 ShiftLeft 提出的 CPG 以及对应的开源项目 Joern，试用了两天，感觉基本满足了需求，于是就有了这篇文章记录一下使用过程。 Joern 是一款基于 CPG 的静态分析工具，可以使用基于 OverflowDb 构建的查询语言，在 CPG 上进行搜索、查询、执行数据流分析，进而帮助安全人员分析代码，发现代码中的缺陷。 关于 Joern 的一些介绍，可以参考官方仓库：https://github.com/joernio/joern CPG 全称 Code Property Graph，是由 AST + CFG + PDG 叠加构建而来（其实还包含 CG 等）的一张图，可以直观的表示程序的结构。 关于 CPG 的内容不再赘述了，有兴趣可以参考： Why Your Code Is A Graph <- 简单易懂，推荐阅读 Code Property Graph Specification 1.1 <- CPG 规范，可以当作字典使用 Finding Stranger Things in Code - STATIC ANALYSIS USING JOERN <- ShiftLeft Inc 的演讲 PPT，推荐阅读 1. Joern VS CodeQL 前面说到了一个想法：“像查询 DB 一样对代码进行查询”，那么最容易直接想到的就是 CodeQL，为啥我没有直接使用 CodeQL 呢，有以下几个原因。 一方面，我个人认为 CodeQL 的语法不太友好，写起来很不顺手，而 Joern 的查询语言就很直接，可能与它是基于 Scala 开发的有关，写起来比较顺滑，不过这一点其实就有点因人而异了，没那么客观。 另一方面，也是最主要的方面，CodeQL 在执行查询之前，需要对项目进行构建或编译，很多情况下，我们拿到的代码构建起来很难，甚至无法构建，这种情况下 CodeQL 就无能为力了。 因此决定试一试 Joern。 2. 基础用法 2.1 安装 Joern 的安装非常简单，前提是需要 Java 11 及以上版本。 按照仓库中的 Quick Installation 安装即可，这里我们就不再赘述了。 2.2 基础语法 我们使用开源项目 java-sec-code 作为测试样例。 启动 joern 并导入待分析的项目： ./joern joern> importCode("../java-sec-code/") Using generator for language: JAVASRC: JavaSrcCpgGenerator Creating project `java-sec-code` for code at `../java-sec-code/` moving cpg.bin.zip to cpg.bin because it is already a database file Creating working copy of CPG to be safe Loading base CPG from: /Users/lightless/program/joern-cli/workspace/java-sec-code/cpg.bin.tmp Code successfully imported. You can now query it using `cpg`. For an overview of all imported code, type `workspace`. Adding default overlays to base CPG The graph has been modifi…

Apache2+Perl 造成的 CRLF 注入问题
type: item
date: 2021-07-21T14:23:48+00:00
description: 0x00 前言 这本来是在工作中遇到的一个 case，觉得比较有趣，就复现并记录一下。 在线上遇到了一个 CRLF 注入的漏洞，请求 http://example.com/test%0d%0aA%3DB 就会触发。起初没怎么在意，以为是 nginx 配置文件编写不当导致的，但是经过粗略的分析，发现 nginx 配置十分简单，只是普通的 proxy_pass 而已，理论上不会出现 CRLF 漏洞，于是浏览了一下这个仓库中其他的文件，发现存在一个 perl 脚本，初步猜测和这块有关系，经过一番分析后，确实如此。 0x01 环境搭建 因为已经看到了有问题的代码，所以直接复现即可。 先安装 Apache2 和 mod_perl，并且开启 mod_cgi。 > apt install apache2 libapache2-mod-perl2 > a2enmod perl > a2enmod cgi 接着修改默认的配置文件/etc/apache2/sites-enabled/000-default.conf，还原出有问题的配置： <VirtualHost *:9999> ServerAdmin webmaster@localhost DocumentRoot /var/www/html ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined ErrorDocument 404 /cgi-bin/missing.pl </VirtualHost> 关键是 ErrorDocument 这行配置，这就是罪魁祸首。其中 missing.pl 的代码如下： #!/usr/bin/perl use CGI; my $q = CGI->new; my $new_uri = $ENV{'REDIRECT_URL'}; printf("Location: http://lightless.me?from=%s\n", $new_uri); printf("Status: 302\n"); print "\n" 如果 perl 脚本无法正常执行，请检查 cgi 模块是否开启并且正确配置，在 /var/log/apache2/error.log 中可能有错误原因。 测试效果如下： # 正常请求 ☁ cgi-bin curl -v "http://localhost:9999/bbbb" * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 9999 (#0) > GET /bbbb HTTP/1.1 > Host: localhost:9999 > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 302 Found < Date: Tue, 20 Jul 2021 14:41:30 GMT < Server: Apache/2.4.29 (Ubuntu) < Location: http://lightless.me?from=/bbbb&a=b < Transfer-Encoding: chunked < Content-Type: text/x-perl < # 有问题的请求 ☁ cgi-bin curl -v "http://localhost:9999/bbbb%0d%0aSet-Cookie:c%3dd%0d%0afoo:" * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 9999 (#0) > GET /bbbb%0d%0aSet-Cookie:c%3dd%0d%0afoo: HTTP/1.1 > Host: localhost:9999 > User-Agent: curl/7.58.0 > Accept: */* > < HTTP/1.1 302 Found < Date: Tue, 20 Jul 2021 14:41:19 GMT < Server: Apache/2.4.29 (Ubuntu) < foo: &a=b < Set-Cookie: c=d < Location: http://lightless.me?from=/bbbb < Transfer-Encoding: chunked < Content-Type: text/x-perl < 可以看到已经成功的注入了 Set-Cookie 头。 0x02 分析 我们先看看 ErrorDocument 这个指令是干什么的，根据官方文档给出的解…

XStream RCE Analysis
type: item
date: 2021-02-19T13:16:17+00:00
description: 0x00 Background XStream 这个组件，主要功能是将 Java 对象和 XML 进行互相转换，也就涉及到了序列化和反序列化。之前一直知道这个组件存在反序列化的 RCE 漏洞，但是一直没有分析过，其他网络上的分析 paper 也就是瞟了几眼。 最近在工作中遇到了这个组件，朋友告诉我扫到了一个 RCE 漏洞，修复方案是升级到最新版本，但是印象中这个组件出现过好几次反序列化的问题了。这不禁勾起了我的好奇心，你说这个东西，会像 fastjson 一样频繁出现问题吗？ 0x01 XStream 101 作为一款基础的反序列化组件，我们先来看看基本用法： package me.lightless; import com.thoughtworks.xstream.XStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; class Foo { private String f1; public String f2; public void bar() { System.out.println(this.f1 + this.f2); } private void baz() { System.out.println(this.f2 + this.f1); } } public class BasicUsage { public static void main(String[] args) { String v1 = "Hello World!"; Map<String, String> map = new HashMap<>(); map.put("aa", "bb"); map.put("cc", "dd"); List<String> list1 = new ArrayList<>(); list1.add("hello"); list1.add("world"); List<Map> list2 = new ArrayList<>(); list2.add(map); list2.add(map); Foo foo = new Foo(); foo.f2 = "f2"; XStream xStream = new XStream(); System.out.println(xStream.toXML(v1) + "\n===\n"); System.out.println(xStream.toXML(map) + "\n===\n"); System.out.println(xStream.toXML(list1) + "\n===\n"); System.out.println(xStream.toXML(list2) + "\n===\n"); System.out.println(xStream.toXML(foo) + "\n===\n"); } } 分别对 String, Map, List, Class 进行序列化，看看结果； <string>Hello World!</string> === <entry> <string>aa</string> <string>bb</string> </entry> <entry> <string>cc</string> <string>dd</string> </entry> === <string>hello</string> <string>world</string> === <entry> <string>aa</string> <string>bb</string> </entry> <entry> <string>cc</string> <string>dd</string> </entry> === <me.lightless.Foo> <f2>f2</f2> </me.lightless.Foo> === 结果还是挺易懂的： Map 中的每一个键值对，都被包装在了 <entry> 标签里，第一项为 key，第二项为 value； List 中的值就直接放到了 标签里； Class 中的字段值，就通过同名的标签来进行赋值； 基本上 XStream 的基础用法就这些了，对于分析漏洞的 poc 来说已经足够了。 0x02 Analysis CVE-2020-26217 XStream 把所有的 CVE 都放到了他们的官网上，很容易找到需要的信息，甚至连 POC 都毫不吝啬的放出来了。这个截至目前为止最新的 RCE CVE 对应的链接：https://x-stream.github.io/CVE-20…

新晋毒瘤ApacheShiro？
type: item
date: 2020-10-02T13:30:42+00:00
description: 0. 背景 准备对最近频发的 ApacheShiro 做个简单的分析和总结，从 1.2.4 开始统计，之前的古老版本就不看了。 1. Deserialization 1.1 shiro-550 AKA CVE-2016-4437 已经写过了：https://lightless.me/archives/java-unserialization-apache-shiro.html 1.2 shiro-721 AKA CVE-2019-12422 已经写过了：https://lightless.me/archives/padding-oracle-attacks.html 2. Authentication Bypass shiro-??? AKA CVE-2016-6802 影响范围：< 1.3.2 由于这个版本还没有 shiro-spring-boot-starter，只能从GitHub上获取项目进行测试； git clone https://github.com/apache/shiro.git shiro-root cd shiro-root && git fetch git checkout shiro-root-1.3.1 接下来还需要在 samples/web/pom.xml 文件中，将 jstl 的版本指定为 1.2。这样就可以正常部署 sample-web 项目了，测试用的 Tomcat 版本为 8.5.34。 关于这个漏洞，网上公开的资料非常少，唯二能找到的有用的就是 GitHub 上的 commit 记录，以及 CVE 的描述信息。 GitHub commit 信息：https://github.com/apache/shiro/commit/b15ab927709ca18ea4a02538be01919a19ab65af CVE 描述：https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-6802 根据 commit 信息，可以定位到缺陷是在 WebUtils.java 中的 getContextPath 方法。 CVE 描述如下： Apache Shiro before 1.3.2 allows attackers to bypass intended servlet filters and gain access by leveraging use of a non-root servlet context path. 根据这个 CVE 描述以及 commit 信息，我们可以知道，漏洞是出现在 Non-ROOT ContextPath 的情况下，获取 ContextPath 时造成的，不过详细信息还无从得知。 既然是与 ContextPath 相关，所以部署样例项目的时候，需要部署到某个 ContextPath 下面，不能使用 ROOT。我们先在 getContextPath 函数的入口处 247 行打个断点，并且发送一个正常的需要登录才能访问的请求看一下这附近代码的处理逻辑； curl -v "http://localhost:8080/sample_web_war/account/index.jsp" 断下来之后可以看到，这里就是取了 ContextPath，并无特殊操作，我们跟着 return 继续向下跟。如果不了解 ContextPath，建议先自行Google一下。 跟到 org.apache.shiro.web.util.WebUtils#getPathWithinApplication 这个函数，可以发现，我们刚刚获取的 ContextPath 其实是为了计算出 RequestURI ，参考画线的两行代码； 继续一直跟到了 org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain 这个函数中，可以看到这里最终其实是获取了 RequestURI 的，这里稍微单步跟一下，不难发现 103 行附近的 for 循环，是用我们获取到 RequestURI 进行一个匹配处理，对匹配到情况执行相应的操作。 我们这里请求的是 account/index.jsp，刚好匹配到了 /account/** 这个模式。在配置文件(shiro.ini)中，/account/** 这个目录是需要登录后的用户才能访问的，猜测这里后续的操作则是校验当前用户是否已经登录，或者说是否有权限； 这里直接 F9 放行，可以发现确实 302 跳转到了登录页面。这样一来，思路就清晰了，出问题的地方是 ContextPath，而 ContextPath 是用来生成 RequestURI 的，而 …

Padding Oracle Attack 浅析
type: item
date: 2019-12-07T04:32:00+00:00
description: 0x00 About POA(Padding Oracle Attack) 最近一段时间 shiro 发了一个有关于 POA的公告：SHIRO-721，把这类攻击方式带了出来，这篇就打算简单讲讲有关于POA的一些东西（其实在 之前，就已经发现了shiro的这个问题了，但是一直没有公开XD 这种攻击方式，最早应该是在2002年的时候被提出来的，具体历史可以参考文末的资料来看看； 0x01 分组密码 这个攻击方式其实还挺有意思的，利用的并不是DES/AES本身的缺陷，而是块加密连接方式的缺陷。这次shiro的问题也主要是由于使用了CBC的块连接方式导致的。在开始分析POA之前，我们先简单复习一下密码学的相关的知识； 我们知道，无论是是AES还是DES，又或是3DES，都是基于“块”对数据进行加密的。我们以最简单的DES来说，先忽略掉整个DES算法的细节，我们将其当做一个黑盒函数来看待。标准的DES分组长度是64bit，也就是一次只能加密64bit的数据，即8byte。但是在实际中，我们加密的数据一般都是远远大于8byte的，那么这个时候要怎么办呢？于是就有了很多种分组加密模式，即按照加密算法的分组长度，将待加密的明文分割成不同的块（block），对每个块单独进行加密，并通过一些特定的方式进行连接，这样一来，就可以任意长度的数据进行加密了。 而POA所涉及到的加密模式，就是CBC（Cipher-block chaining）模式。在这个分组模式中，每个明文在加密之前，都需要与前一个分组产生的密文进行异或操作，我们来看两张图： 这两张图摘取自Wikipedia，分别是CBC的加密与解密的流程，在CBC模式中，每个明文块先与前一个密文块进行异或后，再进行加密。在这种方法中，每个密文块都依赖于它前面的所有明文块。同时，为了保证每条消息的唯一性，在第一个块中需要使用初始化向量。通过这个图很容易看出来了： 每个分组在加密之前，需要和上一个分组的密文进行异或操作； 每个分组在解密之后，需要和前一个分组的密文进行异或操作得到最终的明文； 除了这些，我们还看到了一个IV值，当我们对一个密文块进行操作的时候，由于没有前一个块，所以需要一个IV来替代前一个块进行相应的异或操作，这个IV就是初始向量(Initialization Vector)。 为了方便我们后面的原理分析，我们将DES算法完成后的值成为“中间值”（intermediary value）。具体中间值是什么，可以看下面的这两个流程图。 在分组密码中，还涉及到一个问题没有提到，也是非常重要的一点，就是padding方式。当我们加密的的数据长度，按照分组长度分割后，不够一个分组怎么办？这个时候就设计到数据填充了。要将这个分组通过某种方式进行padding，将其长度补充到一个分组长度。现在常用的补充方式也就是PKCS#5/PKCS#7，补充的方式可以用一段代码来表示： ''' PKCS#5 padding is identical to PKCS#7 padding, except that it has only been defined for block ciphers that use a 64 bit (8 byte) block size. But in AES, there is no block of 8 bit, so PKCS#5 is PKCS#7. ''' BS = AES.block_size pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) unpad = lambda s : s[0:-ord(s[-1])] 补充的数据值，就是当前块分组的长度减去数据长度的值，然后重复补充到分组长度。如果不太明白，可以看下面这个图： 值得一提的是，当待加密的数据长度刚好满足分组长度的倍数时，仍然需要填充一个分组长度。 0x02 POA分析 通过POA，我们可以获取任意明文的值，也可以加密任意明文的值，但是POA其实是有一些前置条件的，并非所有使用CBC的场景都可以被成功的POA。我们现在来创造一些条件： 我们拥有加密后的密文，以及本次加密使用的IV 服务器会返回两种错误消息： 当解密出的值与预设值不同时，返回解密失败 当解密出的值出现padding错误时，返回padding error 为了测试方便，我写了一个简单的脚本来模拟服务器：https://gist.github.com/lightless233/33089f38cd6c0040f9cbcf56414295fd 我们先调用加密接口，获取一段密文来进行测试： > curl http://localhost:5000/encrypt?plain_tex…

XSS With Service Worker
type: item
date: 2019-10-14T14:17:34+00:00
description: 0x00 Service Worker 101 简单的讲，是浏览器在后台独立于网页运行的脚本。可以简单的认为是一个介于客户端和服务端之间的代理服务器，最重要的作用之一就是缓存离线资源。 根据一些文档来看，Service Worker 也是有一定的局限性的。比如： 作为一种JavaScript Worker，它是无法直接访问 DOM 的，不过可以通过响应postMessage 接口发送的消息与控制 Worker 的页面进行通信，由页面侧来完成 DOM 操作。 Service Worker 可以简单的理解为是一个代理，可以拦截网络请求，修改返回内容等。 出于安全考虑，Service Worker 只能在 localhost 和 HTTPS 网站上使用。 既然 Service Worker 拥有代理的功能，那么我们就理所当然的想到，是否可以结合 XSS 一起利用，长期的劫持受害者？答案当然是肯定的，首先看看 Service Worker 的基本使用方法。 一个普通的 Servcie Worker 脚本，通过监听各种事件，完成正常的功能，如下代码： this.addEventListener('install', function (event) { console.log('service worker install!'); }); this.addEventListener('activate', function (event) { console.log('service worker activate!'); }); 这里监听了两个事件，分别是 install 和 activate，对应着 Service Worker 的安装和激活的事件。其他的需要关心的的还有 fetch 事件，主要是用于处理缓存相关的问题，可以拦截请求并修改响应，这里也是我们进行 XSS 攻击着重关心的事件。 这个 JavaScript 脚本需要放到网站的同域名下，没有办法放置到 CDN 等其他位置，具体原因可以参考这里。这样一来，当我们通过 Service Worker 进行深度利用 XSS 漏洞的时候，就没有办法将恶意的 Servcie Worker 脚本放到我们自己的OSS、CDN、服务器上了，需要另辟蹊径。 在了解了 Service Worker 脚本之后，来看一下如何注册一个 Service Worker，仅仅有了 Service Worker 脚本还不够，还需要将其注册给浏览器。 if ('serviceWorker' in window.navigator) { navigator.serviceWorker.register('./sw.js', { scope: './' }) .then(function (reg) { console.log('success', reg); }) .catch(function (err) { console.log('fail', err); }); } 这是一种比较常见的注册方式，开头的 if 判断是用于兼容性判断的，当这个特性刚出现的时候，许多浏览器还不支持，不过近些年来，各大主流浏览器（IE除外）均已支持，所以这个兼容性判断很多情况下就不需要了。Service Worker 与浏览器兼容性 这里需要关心的是 serviceWorker.register() 方法： ServiceWorkerContainer.register(scriptURL, options) .then(function(ServiceWorkerRegistration) { ... }); 这个方法有两个参数： scriptURL：指定 Service Worker 的位置，前面已经讲过，这里不能是一个非同域的URL，而且必须是 HTTPS 或 localhost 的，并且这个脚本的 Content-Type 必须是 text/javascript，有一些等效的 Content-Type 可以参考这里：JavaScript MIME type options：其实最关键的就是 scope ，A service worker's registration scope. 简单理解，就是定义了这个 Service Worker 可以控制的 URL 范围。默认值是 Service Worker 脚本所在的目录。 关于 scope 可能有点难懂，举几个例子，假设域名为 localhost： serviceWorker.register('./sw.js', { scope: './' })：Service Worker 可以控制的 URL 范围为 http://localhost/； serviceWorker.reg…