Jsonp&Cors跨域(同源策略跨域劫持漏洞)

Posted Ocean:)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jsonp&Cors跨域(同源策略跨域劫持漏洞)相关的知识,希望对你有一定的参考价值。

同源策略

同源策略 SOP(Same Origin Policy)是一种约定,他是浏览器最核心也最基本的安全功能,很大程序上防止了XSS、CSRF攻击

一个完整的uri分为以下几个部分,当请求的url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域(不同源),即使不同域名指向同一个IP也不可以

同源对比

html 特殊标签如:

  • <img>

  • <frame>

  • <link>

  • <script>

等标签具有跨域特性,使用这些标签加载远程文件时不会触发同源策略

Demo

使用Think php 5写一个小的demo

class Index extends Controller
{
    public function index() {
        return "hello";
    }
    public function test() {
        $data = ['name' => 'ocean', 'id' => '1'];
        return json_encode($data);
    }
}

依照下图目录结构创建文件,放入代码

在route目录下的route.php文件中添加路由

Route::get('index/test', 'index/test');

我用的是phpstudy,我就直接启动服务了,访问相应目录

test 接口将数据返回到前端

在www目录下新建一个html来测试下同源策略

<html>
    <head>
        <title>test page</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
		<!-- 因为script标签具有跨域特性,所以可以任意加载远程文件 -->
    </head>
    
    <body>
    <script>
        $.ajax({
            type:"get",
            url:"http://127.0.0.1/thinkphp/tp5/public/index.php/index/test",
            dataType:"json",
            success:function(response) {
                console.log(response)
            }
        });
    </script>
    </body>
</html>

发现可以成功取到返回的json数据,在同源策略的允许范围之内

那么跨域可以将请求地址从 127.0.0.1 改成 localhost,不满足同源策略,再次访问url,发现出现很多报错

跨域

跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样(不同源)

虽然在安全层面上同源限制是必要的,但有时同源策略会对我们的合理用途造成影响,为了避免开发的应用受到限制,有多种方式可以绕开同源策略

  • Jsonp
  • Proxy代理
  • CORS
  • nginx反向代理
  • Websocket
  • postMessage

Jsonp

JSONP 本质上是利用 <script><img><iframe> 等标签不受同源策略限制,可以从不同域加载并执行资源,来实现数据跨域传输

JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据

JSONP 的理念就是,与服务端约定好一个回调函数名,服务端接收到请求后,将返回一段 javascript,在这段 Javascript 代码中调用了约定好的回调函数,并且将数据作为参数进行传递。当网页接收到这段 Javascript 代码后,就会执行这个回调函数,这时数据已经成功传输到客户端了

还是以一个demo来说明

首先去构造一个简单的后端,在TP 控制器中返回字符串test();,这就是js中执行函数的语句

<?php
namespace app\\index\\controller;

class Index
{
    public function index() {
        return "test();";
    }   
}   

然后去构造一个前端

<html>
    <head>
        <title>test page</title>
    </head>
    
    <body>
        <script src="http://localhost/thinkphp/tp5/public/index.php/index/index"></script>
    </body>
    
</html>

发现test这个方法没有找到

可以判断后端传过来的 test(); 被执行了,就和在控制台直接执行一样

如果前端页面有test函数呢

    <body>
        <script>
            function test(){
                console.log("test");
            }
        </script>
        <script src="http://localhost/thinkphp/tp5/public/index.php/index/index"></script>
    </body>

再去看浏览器返回结果,发现控制台输出了test,代表test函数执行了啊

既然可以执行那么传数据可不可以呢,让函数接收数据

tp控制器传入数据

class Index
{
    public function index() {
        return "test('ocean');";
    }
}    

前端接收数据

    <body>
        <script>
            function test(value){
                console.log(value);
            }
        </script>
        <script src="http://localhost/thinkphp/tp5/public/index.php/index/index"></script>
    </body>

再次访问html页面,发现数据传过来了

但是可以发现你前端后端都是写好的触发test方法,但是通用性太差了,每一个前端script请求都要匹配一个后端控制器,有没有简化方法呢,答案是肯定的

在html script访问url中加上callback参数

<script src="http://localhost/thinkphp/tp5/public/index.php/index/index?callback=test"></script>

使用callback参数告诉后端,前端用来接收参数的触发方法的方法名,这样后端只要这样写就能做到通用性

class Index
{
    public function index() {
        $callback = $_GET["callback"];
        return $callback.'(12345)';
    }
}    

接下来就使用 jsonp + jquery 来完成这个操作

前端html

<html>
    <head>
        <title>test page</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    </head>
    
    <script>
        $.ajax({
            type:"get",
            url:"http://localhost/thinkphp/tp5/public/index.php/index/index",
            dataType:"jsonp",
            success:function(response){
                console.log(response)
            }
        });
    </script>            
</html>

后端还是之前的

class Index
{
    public function index() {
        $callback = $_GET["callback"];
        return $callback.'(12345)';
    }
}

再次访问html

在network可以看到jquery自动补充了callback,原理和以前的是一样的

JSONP 的优点是:它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行。

JSONP 的缺点是:它只支持 GET 请求,而不支持 POST 请求等其他类型的 HTTP 请求

CORS

跨域资源共享,新增的一组HTTP首部字段,允许服务端其声明哪些源站有权限访问哪些资源,换言之,它允许浏览器向声明了 CORS 的跨域服务器,发出 XMLHttpReuest 请求,从而克服 Ajax 只能同源使用的限制

规范也要求对于非简单请求,浏览器必须首先使用 OPTION 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求,在服务器确定允许后,才发起实际的HTTP请求。对于简单请求、非简单请求以及预检请求的详细资料可以阅读HTTP访问控制(CORS)

HTTP Header

Access-Control-Allow-Origin

响应首部中可以携带这个头部表示服务器允许哪些域可以访问该资源

Access-Control-Allow-Origin: <origin> | *

其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求

Access-Control-Allow-Methods

该首部字段用于预检请求的响应,指明实际请求所允许使用的HTTP方法。其语法如下:

Access-Control-Allow-Methods: <method>[, <method>]*

Access-Control-Max-Age

该首部字段用于预检请求的响应,指定了预检请求能够被缓存多久,其语法如下:

Access-Control-Max-Age: <delta-seconds>

Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。其语法如下:

Access-Control-Allow-Credentials: true

另外,如果要把 Cookie 发送到服务器,除了服务端要带上Access-Control-Allow-Credentials首部字段外,另一方面请求中也要带上withCredentials属性。

但是需要注意的是:如果需要在 Ajax 中设置和获取 Cookie,那么Access-Control-Allow-Origin首部字段不能设置为* ,必须设置为具体的 origin 源站。详细可阅读文章CORS 跨域 Cookie 的设置与获取

Origin

该首部字段表明预检请求或实际请求的源站。不管是否为跨域请求,Origin字段总是被发送。其语法如下:

Origin: <origin>

Access-Control-Request-Method

该首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。其语法如下:

Access-Control-Request-Method: <method>

Access-Control-Request-Headers

该首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。其语法如下:

Access-Control-Request-Headers: <field-name>[, <field-name>]*

一个简单的Demo

后端

<?php
namespace app\\index\\controller;

class Index
{
    public function test() {
        
        $data = ['name' => 'ocean', 'id' => '1'];
        return json_encode($data);
    }
}

前端html

<html>
    <head>
        <title>test page</title>
        <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
		<!-- 因为script标签具有跨域特性,所以可以任意加载远程文件 -->
    </head>
    
    <body>
    <script>
        $.ajax({
            type:"get",
            url:"http://localhost/thinkphp/tp5/public/index.php/index/test",
            dataType:"json",
            success:function(response) {
                console.log(response)
            }
        });
    </script>
    </body>
</html>

访问可以发现跨域成功

对于在 Java Web 项目中,如何在 Servlet 或这 Spring MVC 中配置 CORS 可以阅读文章Spring MVC 实现 CORS 跨域

与 JSONP 的比较

  • JSONP 只能实现 GET 请求,而 CORS 支持所有类型的 HTTP 请求
  • 使用 CORS ,开发者可以是使用普通的 XMLHttpRequest 发起请求和获取数据,比起 JSONP 有更好的错误处理
  • 虽然绝大多数现代的浏览器都已经支持 CORS,但是 CORS 的兼容性比不上 JSONP,一些比较老的浏览器只支持 JSONP

代理proxy

通过中间件来实现,因为浏览器有跨域限制,但是服务器没有限制,所以中间件其实就是服务器(服务器对数据进行转发)

常用于前端项目

https://juejin.cn/post/6956129265368694797

Nginx反向代理

漏洞

JSON相关漏洞(Hijacking+Injection)挖掘技巧及实战案例

JSONP 安全攻防技术

参考链接:

https://www.bilibili.com/video/BV1pA411u7Ji

https://www.jianshu.com/p/f880878c1398

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS

以上是关于Jsonp&Cors跨域(同源策略跨域劫持漏洞)的主要内容,如果未能解决你的问题,请参考以下文章

JSONP跨域和CORS跨域的区别

跨域(jsonp cors)

JSONP && CORS

同源策略与JSONP劫持原理

跨域资源请求 JSONP CORS

浏览器的同源策略和CORS跨域