Do I need to sanitize the callback parameter from a JSONP call?
I would like to offer开发者_运维知识库 a webservice via JSONP and was wondering, if I need to sanitize the value from the callback parameter.
My current server side script looks like this currently (More or less. Code is in PHP, but could be anything really.):
header("Content-type: application/json; charset=utf-8");
echo $_GET['callback'] . '(' . json_encode($data) . ')';
This is a classic XSS-vulnerability.
If I need to sanitize it, then how? I was unable to find enough information about what might be allowed callback strings. I quote from Wikipedia:
While the padding (prefix) is typically the name of a callback function that is defined within the execution context of the browser, it may also be a variable assignment, an if statement, or any other Javascript statement prefix.
You want to ensure the callback is a valid identifier, which can be alphanumeric, underscore, or $. It also cannot be a reserved word (and just to be thorough I would make sure it is not undefined
, NaN
, or Infinity
). This is the test I use:
function valid_js_identifier( $callback ){
return !preg_match( '/[^0-9a-zA-Z\$_]|^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|var|volatile|void|while|with|NaN|Infinity|undefined)$/', $callback);
}
Many of the reserved words are pointless, but some of them could cause errors or infinite loops.
Important: do not just sanitize the input by replacing characters; the modified callback could run without error and the data returned will not be handled properly (or could even be handled by the wrong function). You want to test if the input is valid, and throw an error if it's not. This will avoid unexpected behavior and notify the developer that a different callback is needed.
note: This is a safer, but limited, version of JSONP that does not allow expressions or refinement. I've found it works great for most applications, especially if you are using jQuery and $.getJSON
Yes, when callback
is like
(function xss(x){evil()})
When you echo back from php, will looks like
(function xss(x){evil()})(json)
function xss will run and evil() can be some codes sending cookies to somewhere else.
So, sanitize it to only valid function names, for example, limit it to alphanumeric
Yes.
As described by @YOU an attacker could craft a callback parameter that evaluates to malicious javascript, or worse, malicious Flash.
Validating that the callback isn't a reserved word and is alpha-numeric as described by @Brett-Wejrowski is a good start.
Google, Facebook and Github are mitigating the Rosetta Flash vulnerability by pre-pending an empty comment, like /**/, to the jsonp callback.
Another approach would be to return a safer javascript expression like ExpressJS does:
typeof callbackstring === 'function' && callbackstring(.....);
Yes, you need to sanitize the callback parameter.
JSONP is basically a self-inflicted XSS attack. When (temporarily) inserting of script tag with a url to a different hostname and letting it call a global function or method on your page, it is important to at least take some precautions that you limit "callback" to be nothing more but a callback name.
A callback name should, syntactically, be as simple as an identifier. You could make an allowance for object properties. I would recommend against allowing parentheses, since that might enable function invocations, etc.
Below is an example of a basic API that supports both JSON and JSONP. It is written in PHP (simplified from MediaWiki's API), but similar structures can be created in other programming languages.
<?php
// Simulate the response data
$responseData = [
'foo' => 'bar',
'count' => ['one', 'two', 'three'],
'total' => 3,
];
// Prepare to send the response
$prefix = '';
$suffix = '';
$ctype = 'application/json';
if (isset($_GET['callback'])) {
$ctype = 'text/javascript';
// Sanitize callback
$callback = preg_replace("/[^][.\\'\\\"_A-Za-z0-9]/", '', $_GET['callback']);
$prefix = $callback . '(';
$suffix = ')';
}
// Send the response
header("Content-Type: $ctype; charset=UTF-8", true);
print $prefix . json_encode($responseData) . $suffix;
exit;
精彩评论