lightless blog - Web安全
type: channel
link: https://www.lightless.me/category/web_security/
items: SignMe 签名插件
items: BurpAuthTest 越权检测插件
items: Analyze ApacheCommonsText With Joern
items: Apache2+Perl 造成的 CRLF 注入问题
items: XStream RCE Analysis
items: Padding Oracle Attack 浅析
items: Same Origin Method Execution
items: 再谈同源策略

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 内置…

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…

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…

Same Origin Method Execution
type: item
date: 2017-07-26T15:48:00+00:00
description: 0x00 什么是Same Origin Method Execution 先贴一篇paper，这个是Ben Hayak在Black Hat Eorope 2014演讲的题目，是关于如何利用JSONP之类的callback执行方法，特别是在对callback的参数名进行过滤之后的情况下。 我们都知道，JSONP可以利用callback的特性，来跨域传输数据，毫无疑问也可以同域间传递数据。我们试着考虑一下这种场景，某个网站在编辑文章的地方提供了一个颜色选择器，用来调整文字颜色，这个颜色选择器是一个弹出的窗口，通过一个callback参数与原页面进行通信，具体一下可能是下面这样： http://a.com/color.php?callback=get_color color.php的部分内容可能如下： 也就是当访问color.php?callback=get_color之后，会在当前页面执行get_color函数，进行数据处理之类的操作。如果开发者不注意的话，就会出现安全隐患。我们可以利用这个callback的缺陷，来执行用户不期望的敏感操作。注意一点，这并不是CSRF，一旦CSRF有了token就无法利用了（在不考虑同源XSS的情况下），但是SOME却是可以绕过这种限制进行敏感操作。 但是这种漏洞也是有着利用限制的，这个JSONP如果返回了类似Content-T ype: application/json的值，是无法利用的，因为这段代码并不会执行。通常情况下只有返回Content-Type: text/html才有利用的机会。有个较为简单的判断方法，当修改了URL中的callback值后，看看console中是否会有类似找不到方法xx，未定义的方法xx之类的提示，如果有的话，可以简单的认为，这个callback对应的函数会被执行。 0x01 利用前的理论基础 在JavaScrip中，我们可以通过window.open()方法打开一个新的窗口，而且新窗口与原窗口是可以互相交互的。 page1.html page2.html 从上面的代码可以看出，原页面可以通过变量获取到新页面，而新页面又可以通过window.opener获取到原页面，这也为SOME提供了利用基础，当然前提条件是page1和page2需是同源。 我们有了一个可以执行任意方法的接口，又知道了可以通过opener获取到原页面，那么我们就可以利用这种特性让用户进行他们不愿意的操作。 0x02 利用流程 准备一个some.html页面 让some.html打开some2.html页面 some.html跳转到目标页面（含有敏感操作dom的页面，例如含有点一下就删除订单、点一下就增加用户之类的敏感操作的页面） 在some.html跳转完成后，让some2.html跳转到发现的JSONP接口，并将callback修改为我们的payload 利用完成。 光这样看起来会感觉很难过，我把相关文件的代码贴出来。 info.php，JSONP接口，获取用户信息； <?php $callback = empty($_GET["callback"]) ? "jsCallback" : $_GET["callback"]; $data = [ "username" => "lightless", "avatar" => "http://cdn.lightless.me/images/01.jpg", ]; $data = json_encode($data); echo " "; secret.html，含有敏感DOM的页面，其中有个敏感按钮，点击会触发一些操作 Secret Button Access Token: Secret Token: 上面两部分假设分别是http://b.com/user.php?callback=view以及http://b.com/secret.html，其中secret.html中的内容以及按钮只有管理员才可以查看与操作，其他人是无权查看的。我们的目标就是『让管理员点击Secret Button按钮』。 接下来我们构造POC。首先要有个same.html，他负责打开新的页面，并且将当前页面重定向到含有敏感DOM的页面即secret.html。 same.html 最后的same2.html页面，需要等待same.html重定向完成后，重定向到JSONP接口，利用这个接口对window.opener（现在已经变成了b.com/secret.html）执行操作。 same2.html 这样当用户访问http://attacker.com/same.html的时候，实际上已经执行了b.com上的敏感操作。我这里录制了一段视频来演示这个利用过程：https://c1.l…

再谈同源策略
type: item
date: 2017-07-24T15:04:00+00:00
description: 同源策略应该是学习Web安全时最最基础的内容，时隔多年再回过头来仔细温习一下，发现了不少当初漏掉的细节，结合这么多年的安全经验，重新总结一下同源策略相关的内容。 0x00 何为同源策略 如果两个页面拥有相同的协议（http、https）、相同的端口（如果其中一个指定了端口）、相同的host，那么就可以认为这两个页面是同源的。简单的讲，同源策略就是同协议、同端口、同host这样的一个三元组，这里给出几个例子： 我们将所有的URL都与第一个URL相比： 第二个与第一个URL是相同的，原因是满足我们上面提到的三元组（协议、端口、host） 第三个与第一个相比，其协议不同； 第四个与第一个相比，其端口不同； 第五个与第一个相比，其host不同； 那么这种限制有什么作用呢？同源策略主要是限制了页面最后那个的脚本从另一个源加载资源时的行为，这对于防范恶意页面是一种很好的防御机制，如果恶意脚本请求了非同源的一个东西，那么这种行为就很可能因为同源策略的限制被浏览器拒绝，从而在某种程度上缓解了攻击。 对于about:blank和javascript:这种特殊的URL，他们的源应当是继承自加载他们的页面的源，他们本身并没有『源』的概念。 0x01 HTTP请求控制 我们已经知道了，浏览器会根据同源策略允许或拒绝加载某些资源，但是又一个问题由此而生，我们的网站通常会将静态文件（CSS,JS,图片）等放置在CDN上，那么CDN与当前域必然是不同源的，但是神奇的是，这些网站可以正常加载出他们需要的资源并展示给用户，这里为什么又不受同源策略的影响呢？ 再比如在使用XMLHttpRequest的时候，又会因为同源策略的限制无法发出请求，那么到底什么情况下会触发同源策略呢？总体来说，页面跨域的行为主要会分为三类，分别是： Cross-origin write Cross-origin read Cross-origin embedding 在这三种行为之中，通常情况下只有Cross-origin read是不被允许的，其余的两种是允许的，例如Cross-origin write中的links，重定向以及表单提交，Cross-origin embedding中的资源嵌入。 那么问题又来了，何种资源是允许嵌入的呢？MDN文档中也给出了一些例子： 标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到。 标签嵌入CSS。由于CSS的松散的语法规则，CSS的跨域需要一个设置正确的Content-Type消息头，不同浏览器有不同的限制。 嵌入图片。支持的图片格式包括PNG,JPEG,GIF,BMP,SVG,... 和 嵌入多媒体资源。 , 和 <applet>的插件。 @font-face引入的字体。一些浏览器允许跨域字体（cross-origin fonts），一些需要同源字体（same-origin fonts）。 和 载入的任何资源。站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。 这样一来，可以通过 标签加载文件而不受同源策略的影响这件事情就明白了。 0x02 CORS又是什么东西 刚刚我们已经知道了，一些标签嵌入外域数据的时候，是不会受到同源策略的影响，但是如果我们在 脚本中想要获取外域的数据时，因为同源策略的干扰，就显的格外麻烦。出于安全考虑，浏览器通常情况下都会限制从script标签内部发起的跨域HTTP请求，比如前文提到的XMLHttpRequest，就会受到同源策略的影响，进而只能将数据发到同域内。 这里需要说明一点，这里的跨域请求可能不是浏览器直接拦截掉了，而是跨站请求发起了，但是返回结果被浏览器拦截了，请求实际上已经发送到了后端服务器。在Chrome和Firefox上，对于从https到http的跨域是会直接拦截，请求都无法发送成功。 于是就有了CORS策略，允许Web应用程序进行跨域访问。CORS的全称是cross-origin sharing stander，跨域资源共享标准。简单的讲，这个标准允许在以下的几个场景中发起跨域请求： 由XMLHttpRequest或Fetch等发起的跨域请求； Web字体，通过@font-face进行跨域调用； WebGL贴图； 使用 drawImage 将 Images/video 画面绘制到 canvas； 样式表（使用 CSSOM）； scripts； 在这些允许跨域的场景之中，我们作为安全人员，最关心的自然是scripts部分。 0x03 进击的CORS CORS通过一些特殊的HTTP头来确保哪些源站可以请求哪些资源，除此之外，如果这个请求会对服务器的数据产生修改的可能（这个说法并不是很准确，后面会详细阐述），将会在跨域之前发起一个preflight请求进行检查，如果检查不通过，…