Нужно ли дезинфицировать параметр обратного вызова из вызова JSONP?
Я хотел бы предложить веб-сервис через JSONP, и мне было интересно, если мне нужно очистить значение из параметра обратного вызова.
Мой текущий серверный скрипт выглядит следующим образом (более или менее. Код написан на PHP, но на самом деле может быть чем угодно.):
header("Content-type: application/json; charset=utf-8");
echo $_GET['callback'] . '(' . json_encode($data) . ')';
Это классическая XSS-уязвимость.
Если мне нужно его продезинфицировать, то как? Мне не удалось найти достаточно информации о том, что может быть разрешено строк обратного вызова. Я цитирую из Википедии:
Хотя padding (префикс) обычно является именем функции обратного вызова, определенной в контексте выполнения браузера, он также может быть присваиванием переменной, оператором if или любым другим префиксом оператора Javascript.
4 ответа
Да, когда callback
как
(function xss(x){evil()})
Когда вы вернетесь от php, будет выглядеть
(function xss(x){evil()})(json)
будет запущена функция xss, а evil() может быть некоторыми кодами, отправляющими куки куда-то еще.
Таким образом, очистите его только для допустимых имен функций, например, ограничьте его буквенно-цифровым
Вы хотите убедиться, что обратный вызов является допустимым идентификатором, который может быть буквенно-цифровым, подчеркиванием или $. Это также не может быть зарезервированным словом (и просто чтобы быть внимательным, я бы убедился, что это не undefined
, NaN
, или же Infinity
). Это тест, который я использую:
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
Да.
Как описывает @YOU, злоумышленник может создать параметр обратного вызова, который оценивает вредоносный JavaScript или, что еще хуже, вредоносный Flash.
Подтверждение того, что обратный вызов не является зарезервированным словом и является буквенно-цифровым, как описано в @Brett-Wejrowski, является хорошим началом.
Google, Facebook и Github смягчают уязвимость Rosetta Flash, предварительно ожидая пустой комментарий, такой как /**/, к обратному вызову jsonp.
Другой подход - вернуть более безопасное выражение javascript, как это делает ExpressJS:
typeof callbackstring === 'function' && callbackstring(.....);
Да. Поскольку JSONP в основном является самозабвенной атакой XSS ((временно или не временно), вставляющей тег сценария в другое имя хоста и позволяющим ему вызывать глобальную функцию или метод глобального объекта), важно, по крайней мере, принять некоторые меры предосторожности, которые Вы ограничиваете "обратный вызов", чтобы быть не чем иным как обратным вызовом.
Так что в основном любой действительный идентификатор. И вы могли бы сделать исключение для членов объекта. Я бы не рекомендовал разрешать использование скобок для простоты, поскольку это позволяет вызывать функции, а что нет.
Вот пример того, как я мог бы создать базовый API, который поддерживает как JSON, так и JSONP. Приведенный ниже пример написан на PHP (упрощенный от того, как работает API MediaWiki), но подобные структуры могут быть созданы на других языках программирования.
<?php
$responseData = array(
'foo' => 'bar',
'count' => array('one', 'two', 'three'),
'total' => 3,
);
$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 = ')';
}
header('Content-Type: ' . $ctype . '; charset=UTF-8', true);
print $prefix . json_encode($responseData) . $suffix;
exit;