我是否需要从JSONP调用中清除回调参数?

时间:2010-05-05 21:46:08

标签: jsonp xss

我想通过JSONP提供网络服务,并且想知道,如果我需要清理回调参数中的值。

我当前的服务器端脚本现在看起来像这样(或多或少。代码在PHP中,但可能是真的。):

header("Content-type: application/json; charset=utf-8");
echo $_GET['callback'] . '(' . json_encode($data) . ')';

这是一个典型的XSS漏洞。

如果我需要消毒它,那怎么样?我无法找到有关可能允许的回调字符串的足够信息。我引自Wikipedia

  

虽然padding(前缀)通常是在浏览器的执行上下文中定义的回调函数的名称,但它也可以是变量赋值,if语句或任何其他Javascript语句前缀。

4 个答案:

答案 0 :(得分:11)

您希望确保回调是有效的标识符,可以是字母数字,下划线或$。它也不能是一个保留字(为了彻底,我会确保它不是undefinedNaNInfinity)。这是我使用的测试:

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);
}

许多保留字是毫无意义的,但其中一些可能导致错误或无限循环。

重要提示:不要只是通过替换字符来清理输入;修改后的回调可以无错误地运行,并且返回的数据将无法正确处理(甚至可能由错误的函数处理)。您想测试输入是否有效,如果不是则抛出错误。这将避免意外行为并通知开发人员需要不同的回调。

注意:这是一个更安全但有限的JSONP版本,不允许表达式或细化。我发现它适用于大多数应用程序,特别是如果你使用jQuery和$.getJSON

答案 1 :(得分:10)

是的,当callback

相似时
(function xss(x){evil()})

当你从php回显时,看起来像

(function xss(x){evil()})(json)

函数xss将运行而evil()可以是某些代码将cookie发送到其他地方。

因此,将其清理为仅有效的函数名称,例如,将其限制为字母数字

答案 2 :(得分:4)

正如@YOU所描述的,攻击者可以制作一个回调参数来评估恶意javascript,或者更糟糕的是malicious Flash

验证回调不是保留字,并且是@ Brett-Wejrowski描述的字母数字是一个好的开始。

谷歌,Facebook和Github通过预先挂起对jsonp回调的空注释(如/ ** /)来缓解Rosetta Flash漏洞。

另一种方法是返回一个更安全的javascript表达式,如ExpressJS:

typeof callbackstring === 'function' && callbackstring(.....);

答案 3 :(得分:3)

是的,您需要清理回调参数。

JSONP基本上是一种自我造成的XSS攻击。当(暂时)将带有url的脚本标记插入到不同的主机名并让它在页面上调用全局函数或方法时,至少要采取一些预防措施,将“回调”限制为仅仅是一个回调名。

回调名称在语法上应该像标识符一样简单。您可以为对象属性留出余量。我建议不要使用括号,因为这可能会启用函数调用等。

以下是支持JSON和JSONP的基本API示例。它是用PHP编写的(从MediaWiki的API简化),但可以在其他编程语言中创建类似的结构。

<?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;