简单描述

JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
JSONP实现跨域请求的原理简单的说,就是动态创建<script>标签,然后利用<script>的src 不受同源策略约束来跨域获取数据。

JSONP 由两部分组成:回调函数数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。

动态创建<script>标签,设置其src,回调函数在src中设置:

var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

在页面中,返回的JSON作为response参数传入回调函数中,我们通过回调函数来来操作数据。

function handleResponse(response){
    // 对response数据进行操作代码
}

上面是简单直接的对JSONP 的描述,可能有些人不是很懂,我们下面一步一步分析

层层深入

先通过一个简单的实例简单的理解一下同源策略的作用

首先我们在本地写入
1.html

<!DOCTYPE html>
<html>
<head>
    <title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
    function jsonhandle(data){
        alert("age:" + data.age + "name:" + data.name);
    }
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript">
    $(document).ready(function(){
        $.ajax({
            type : "get",
            url : "http://check.k0rz3n.com/test.php?id=1",
            dataType: "jsonp",//指定我们的请求是一个 jsonp 的请求
            success : function(data) {//success 指定的是默认的回调函数
                jsonhandle(data);
            }

        });
    });
</script>
</body>
</html>

我服务器上的test.php会返回json 格式的数据给客户端

test1.php

<?php
header('Content-Type:application/json; charset=utf-8');
$data = array('age'=>19,'name'=>'jianshu');
exit(json_encode($data));
?>

如果正常访问的话,那么我们的浏览器应该会弹出对话框,结果我们会得到这样的结果

此处输入图片的描述

可以看到,浏览器发现这是一个跨域的请求,但是他在服务器的返回头中缺没有发现
Access-Control-Allow-Origin 值允许 http://localhost 的访问,于是就拦截了。

也就是说,虽然浏览器受到了同源策略的限制,不允许实现跨域访问,但是由于在开发过程中前后端的交互过程中不可避免地会涉及到跨域的请求(设计同源策略的人想必也发现了这个问题),于是设计者给我们留了一个后门,就是只要服务器响应头中返回允许这个源的选项,那么跨域请求就会成功。(这里纠正一个误区,不要认为浏览器默认支持同源策略就意味着不同源的请求就不能发出去,其实还是能发出去的,只是要看响应头而已。)

我们知道在页面中有几个东西是对同源策略免疫的,那就是 <img> 的src 、<link> 的 href 还有就是<script>的 src , JSONP 就是利用 script 标签的sec 属性实现跨区域请求的

script标签的请求不论是不是同源一律不受同源策略的限制,那我们就找到了解决跨域访问的方法(似乎这个方法一开始就存在…..)

我们改变一下代码,本地直接通过script标签请求服务器上的js,js 的内容就是调用参数已经传进去的本地的js函数

2.html

<!DOCTYPE html>
<html>
<head>
    <title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
    function jsonhandle(data){
        alert("age:" + data.age + "name:" + data.name);
    }
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript" src="http://check.k0rz3n.com/remote.js"></script>
</body>
</html>

remote.js

jsonhandle({
    "age" : 15,
    "name": "John",
})

注意:

(1)远程的js 代码不需要script标签
(2)这其实也给了我们一些启示,就是我们使用 callback 函数请求的页面实际上类型是javascript 的类型,我们可以在这里看一下浏览器会将哪些类型当做 javascript 解析:https://mathiasbynens.be/demo/javascript-mime-type

下图可以看到我们成功利用<script>实现了跨域的访问。

此处输入图片的描述

那JSONP和这个有啥关系,感觉已经实现跨域了还没有提到一点JSONP,上面说JSONP是基于script标签的,个人感觉JSONP的优势就是能够实现呢比较方便的函数选择,传一个参数就行了,不用像直接调用那样必须要换js文件。

真相浮现

先用下面的代码模拟jsonp的调用过程方便大家更好的理解jsonp的运行过程

3.html

<!DOCTYPE html>
<html>
<head>
    <title>GoJSONP</title>
</head>
<body>
<script type="text/javascript">
    function jsonhandle(data){
        alert("age:" + data.age + "name:" + data.name);
    }
</script>
<script type="text/javascript" src="jquery-3.3.1.min.js">
</script>
<script type="text/javascript">
    $(document).ready(function(){
        var url = "http://check.k0rz3n.com/test1.php?id=1&callback=jsonhandle";
        var obj = $('<script><\/script>');
        obj.attr("src",url);
        $("body").append(obj);
    });
</script>
</body>
</html>

test1.php

<?php
$data = array(
    'age' => 20,
    'name' => 'dada',
);

$callback = $_GET['callback'];

echo $callback."(".json_encode($data).")";
return;

我们在scipt标签里面给出的链接是我远程服务器的一个php的代码,我给这个文件传递了一个参数,作为我要调用的函数。服务器接收到这个参数以后把它当做函数名,并给这个函数传递了一个json的值作为用户调用的函数的参数,最终实现调用

下面是调用成功的截图

此处输入图片的描述

实际上,jquery 给我们提供了现成的接口,我们可以不用这么麻烦

4.html

<!DOCTYPE html>
<html>
<head>
    <title>GoJSONP</title>
</head>
<body>
<script type="text/javascript" src="jquery-3.3.1.min.js"></script>
<script type="text/javascript">
function jsonhandle(data){
    alert("age:" + data.age + "name:" + data.name);
}
</script>
<script type="text/javascript">
    $(document).ready(function(){
        $.ajax({
            type : "get",
            url : "http://check.k0rz3n.com/test1.php?id=1",
            dataType: "jsonp",
            jsonp:"theFunction", //指定回调函数在 URL 中的参数名(不指定默认为 callback)
            jsonpCallback: "jsonhandle",//指定回调函数名称(如果不指定,服务器会随机分配一个jQueryxxx 的名字)
            success : function(data) {
                console.info("调用success");
            }

        });
    });
</script>
</body>
</html>

这时候的请求的 URL 就像下面这个样子:

http://check.k0rz3n.com/test1.php?id=1&theFunction=jsonhandle

服务器端页面为:

test2.php

<?php
$data = array(
    'age' => 20,
    'name' => 'dada',
);

$callback = $_GET['theFunction'];

echo $callback."(".json_encode($data).")";
return;

怎么样,大概理解了吧,其实可以用一个非常形象的例子说明:

幼稚园吃午饭,小明吧贴有自己名字的碗(回调函数)给了幼稚园阿姨(服务器),阿姨给小明盛好饭(json参数)以后又把碗还给了小明。

就是这样的一个过程。

相关拓展:JSONP攻击

1.JSONP 跨域劫持

实际上就是由于服务器端对JSONP 的请求来源的检查不严格导致的

攻击者模拟用户向有漏洞的服务器发送JSONP请求,然后就获取到了用户的某些信息,再将这些信息发送到攻击者可控的服务器

2.JSONP 跨域劫持token 实现CSRF

通过 jsonp 发起请求,得到泄露的 csrf_token 然后,利用这个token 实现CSRF 攻击

3.Referer 头的绕过

在攻击过程中可能会涉及到 referer 头的绕过

  1. data:URL
    为了逃避他的检测我们可以选择不发送referer这个头,那么怎么做呢?这就涉及到 data:URL 头

为了构造一个不带HTTP Referer的请求,我们可以滥用data URI方案。因为我们正在处理的代码包含了引号,双引号,以及其他一些被阻止的语句,接着使用base64编码我们的payload(回调函数定义以及脚本包含)
data:text/plain;base64our_base64_encoded_code:

以下3个HTML标签允许我们使用data URI方案:

iframe (在src属性中) – Internet Explorer下不工作
embed (在src属性中) – Internet Explorer及Microsoft Edge下不工作
object (在data属性中) – Internet Explorer及Microsoft Edge下不工作

2.从HTTPS向HTTP发起请求

如果目标网站可以通过HTTP访问,也可以通过将我们的代码托管在一个HTTPS页面来避免发送HTTP Referer。如果我们从HTTPS页面发起一个HTTP请求,浏览器为了防止信息泄漏是不会发送Referer header。以上我们要将恶意代码托管在一个启用了HTTPS的站点。
注意:由于mixed-content安全机制,在浏览器默认设置下是不会工作的。需要受害者手动允许浏览器发出的安全警告。

参考链接

https://blog.csdn.net/u011897301/article/details/52679486
https://blog.csdn.net/u014607184/article/details/52027879
http://www.91ri.org/13407.html
http://www.freebuf.com/articles/web/70025.html
http://www.freebuf.com/articles/web/126347.html
https://www.cnblogs.com/chiangchou/p/jsonp.html