原文出处: hengyunabc
前言JSON(Object Notation),可以说是事实的浏览器,服务器交换数据的标准了。目测其它的格式如XML,或者其它自定义的格式会越来越少。
为什么JSON这么流行?
和JavaScript无缝对接是一个原因。还有一个重要原因是可以比较轻松的实现跨域。如果是XML,或者其它专有格式,则很难实现跨域,要通过flash之类来实现。任何一种数据格式,如何解析处理不当,都会存在安全漏洞。下面扯谈下JSON相关的一些安全东东。
在介绍之前,先来提几个问题:
JSON注入有的时候,可能是为了方便,有人会手动拼接下JSON,但是这种随手代码,却可能带来意想不到的安全隐患。
第一种方式,利用字符串拼接:
String user = "test01"; String password = "12345', admin:'true"; String json = "{user:'%s', password:'%s'}"; System.out.println(String.format(json, user, password)); //{user:'test01', password:'12345', admin:'true'}用户增加了管理员权限。
第二种,利用Parameter pollution, 类似http parameter pollution
String string = "{user:'test01',password:'hello', password:'world'}"; JSONObject parse = JSON.parseObject(string); String password = parse.getString("password"); System.out.println(password); //world当JSON数据key重复了会怎么处理?大部分JSON解析库都是后面的参数覆盖了前面的。
下面的演示了修改别人密码的例子:
//user%3Dtest01%26password%3D12345%27%2Cuser%3Dtest02" //user=test01&password=12345',user=test02 HttpServletRequest request = null; String user = request.getParameter("user"); //检查test01是否登陆 String password = request.getParameter("password"); String content = "{user:'" + user + "', password:'" + password + "'}"; User user = JSON.parseObject(content, User.class); //{"password":"12345","user":"test02"} updateDb(user);所以说,不要手动拼接JSON字符串
浏览器端应该如何处理JSON数据?像eval这种方式,自然是不能采用的。
现在的浏览器都提供了原生的方法 JSON.parse(str) 来转换为JS对象。如果是IE8之前的浏览器,要使用这个库来解析:https://github.com/douglascrockford/JSON-js
参考:
jQuery里内置了JSON解析库
JSONP Callback注入简单介绍jsonp的工作原理。
jsonp工作原理:用js在document上插入一个<script>标签,标签的src指向远程服务器的API地址。客户端和服务器约定一个回调函数的名字,然后服务器返回回调函数包裹着的数据,然后浏览器执行回调函数,取得数据。
比如jquery是这样子实现的:
var url = ':8080/testJsonp?callback=?'; $.getJSON(url, function(data){ alert(data) });jquery自动把?转成了一个带时间戳特别的函数(防止缓存):
:8080/testJsonp?callback=jQuery1102045087050669826567_1386230674292&_=1386230674293相当于插入了这么一个<script>标签:
<script src=":8080/testJsonp?callback=jQuery1102045087050669826567_1386230674292&_=1386230674293"></script>服务器返回的数据是这样子的:
jQuery1102045087050669826567_1386230674292({'name':'abc', 'age':18})浏览器会执行直接执行这个JS函数。
所以,如果在callback函数的名字上做点手脚,可以执行任意的JS代码。所以说callback名字一定要严格过滤。当然,callback函数的名字通常是程序自己控制的,但是不能排除有其它被利用的可能。那么callback函数的名字,如何过滤?应当只允许合法的JS函数命名,用正则来匹配应该是这样子的:
^[0-9a-zA-Z_.]+$正则可能比较慢,可以写一个函数来判断:
static boolean checkJSONPCallbackName(String name) { try { for (byte c : name.getBytes("US-ASCII")) { if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') { continue; } else { return false; } } return true; } catch (Throwable t) { return false; } }实际上对callback函数名字进行严格检验还有其它的一个好处,就是防范了很多UTF-7编码攻击。因为UTF-7编码的头部都是带有特殊字符的,如”+/v8″,”+/v9″,这样就过滤掉非法编码的请求了。
update: 2016-3-22
spring4.1里有一个AbstractJsonpResponseBodyAdvice ,里面是用正则匹配来判断是否合法的jsonp函数名。
public abstract class AbstractJsonpResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice { /** * Pattern for validating jsonp callback parameter values. */ private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*"); jsonp的请求的验证jsonp在使用的时候,还有容易犯的错误是没有验证用户的身份。
第一,操作是否是用户自己提交的,而不是别的网页用<script>标签,或者用<form>提交的
所以要检查request的refer,或者验证token。这个实际是CSRF防护的范畴,但是很容易被忽略。
第二,要验证用户的权限。
很多时候,可能只是验证了用户是否登录 。却没有仔细判断用户是否有权限。比如通过JSONP请求修改了别的用户的数据。所以说,一定要难证来源。判断refer,验证用户的身份。
到乌云上搜索,可以找到不少类似的漏洞,都是因为没有严格验证用户的权限。?q=jsonp
JSON hijacking在JS里可以为对象定义一些setter函数,这样的话就存在了可以利用的漏洞。比如在浏览器的JS Console里执行:
window.__defineSetter__('x', function() { alert('x is being assigned!'); }); window.x=1;会很神奇地弹出一个alert窗口,说明我们定义的setter函数起作用了。
结合这个,当利用<script>标签请求外部的一个JSON API时,如果返回的是数组型,就可以利用窃取数据。比如有这样的一个API:
返回的数据是JSON Array:
{'user':'test01','age':18},{'user':'test02,'age':19},{'user':'test03','age':20}]