Skip to content

Commit

Permalink
feat: add code injection via template (#961)
Browse files Browse the repository at this point in the history
  • Loading branch information
lirantal authored Jun 9, 2021
1 parent 6fa7510 commit 01c7957
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 40 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,39 @@ Here are the exploitable vulnerable packages:

* Open Redirect
* NoSQL Injection
* Code Injection
* Command execution
* Cross-site Scripting (XSS)
* Security misconfiguration exposes server information
* Insecure protocol (HTTP) communication

#### Code injection

The page at `/account_details` is rendered as an Handlebars view.

The same view is used for both the GET request which shows the account details, as well as the form itself for a POST request which updates the account details. A so-called Server-side Rendering.

The form is completely functional. The way it works is, it receives the profile information from the `req.body` and passes it, as-is to the template. This however means, that the attacker is able to control a variable that flows directly from the request into the view template library.

You'd think that what's the worst that can happen because we use a validation to confirm the expected input, however the validation doesn't take into account a new field that can be added to the object, such as `layout`, which when passed to a template language, could lead to Local File Inclusion (Path Traversal) vulnerabilities. Here is a proof-of-concept showing it:

```sh
curl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{"layout": "./../package.json"}' 'http://localhost:3001/account_details'
```

Actually, there's even another vulnerability in this code.
The `validator` library that we use has several known regular expression denial of service vulnerabilities. One of them, is associated with the email regex, which if validated with the `{allow_display_name: true}` option then we can trigger a denial of service for this route:

```sh
curl -X 'POST' -H 'Content-Type: application/json' --data-binary "{\"email\": \"`seq -s "" -f "<" 100000`\"}" 'http://localhost:3001/account_details'
```

The `validator.rtrim()` sanitizer is also vulnerable, and we can use this to create a similar denial of service attack:

```sh
curl -X 'POST' -H 'Content-Type: application/json' --data-binary "{\"email\": \"[email protected]\", \"country\": \"nop\", \"phone\": \"0501234123\", \"lastname\": \"nop\", \"firstname\": \"`node -e 'console.log(" ".repeat(100000) + "!")'`\"}" 'http://localhost:3001/account_details'
```

#### Open redirect

The `/admin` view introduces a `redirectPage` query path, as follows in the admin view:
Expand Down
5 changes: 4 additions & 1 deletion app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var fileUpload = require('express-fileupload');
var dust = require('dustjs-linkedin');
var dustHelpers = require('dustjs-helpers');
var cons = require('consolidate');
const hbs = require('hbs')

var app = express();
var routes = require('./routes');
Expand All @@ -32,6 +33,7 @@ var routesUsers = require('./routes/users.js')
app.set('port', process.env.PORT || 3001);
app.engine('ejs', ejsEngine);
app.engine('dust', cons.dust);
app.engine('hbs', hbs.__express);
cons.dust.helpers = dustHelpers;
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
Expand All @@ -48,7 +50,8 @@ app.get('/', routes.index);
app.get('/login', routes.login);
app.post('/login', routes.loginHandler);
app.get('/admin', routes.admin);
app.get('/account_details', routes.account_details);
app.get('/account_details', routes.get_account_details);
app.post('/account_details', routes.save_account_details);
app.post('/create', routes.create);
app.get('/destroy/:id', routes.destroy);
app.get('/edit/:id', routes.edit);
Expand Down
85 changes: 85 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"express": "4.12.4",
"express-fileupload": "0.0.5",
"file-type": "^8.1.0",
"hbs": "^4.0.4",
"humanize-ms": "1.0.1",
"jquery": "^2.2.4",
"lodash": "4.17.4",
Expand All @@ -43,7 +44,8 @@
"st": "0.2.4",
"stream-buffers": "^3.0.1",
"tap": "^11.1.3",
"typeorm": "^0.2.24"
"typeorm": "^0.2.24",
"validator": "^13.5.2"
},
"devDependencies": {
"browserify": "^13.1.1",
Expand Down
39 changes: 33 additions & 6 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var streamBuffers = require('stream-buffers');
var readline = require('readline');
var moment = require('moment');
var exec = require('child_process').exec;
var validator = require('validator');

// zip-slip
var fileType = require('file-type');
Expand Down Expand Up @@ -59,12 +60,38 @@ exports.admin = function (req, res, next) {
});
};

exports.account_details = function (req, res, next) {
return res.render('account_details', {
title: 'Account details',
granted: true,
});
};
exports.get_account_details = function(req, res, next) {
// @TODO need to add a database call to get the profile from the database
// and provide it to the view to display
const profile = {}
return res.render('account.hbs', profile)
}

exports.save_account_details = function(req, res, next) {
// get the profile details from the JSON
const profile = req.body
// validate the input
if (validator.isEmail(profile.email, { allow_display_name: true })
// allow_display_name allows us to receive input as:
// Display Name <email-address>
// which we consider valid too
&& validator.isMobilePhone(profile.phone, 'he-IL')
&& validator.isAscii(profile.firstname)
&& validator.isAscii(profile.lastname)
&& validator.isAscii(profile.country)
) {
// trim any extra spaces on the right of the name
profile.firstname = validator.rtrim(profile.firstname)
profile.lastname = validator.rtrim(profile.lastname)

// render the view
return res.render('account.hbs', profile)
} else {
// if input validation fails, we just render the view as is
console.log('error in form details')
return res.render('account.hbs')
}
}

function adminLoginSuccess(redirectPage, res) {
console.log({redirectPage})
Expand Down
44 changes: 44 additions & 0 deletions views/account.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

<style>
strong {font-weight: bold}
</style>
{{#if firstname}}
<h1 id="page-title">Account details for: {{firstname}}</h1>
<center>
<h3 style="color: green">details saved</h2>
</center>
{{else}}
<h1 id="page-title" style="color: red">Account details missing</h1>
{{/if}}

<div id="list">
<form action="/account_details" method="POST" accept-charset="utf-8">
<div class="item-new">
<center>First name</center>
<input class="input" type="text" name="firstname" value="{{firstname}}" />
<br/>

<center>Last name</center>
<input class="input" type="text" name="lastname" value="{{lastname}}" />
<br/>

<center>Country</center>
<input class="input" type="text" name="country" value="{{country}}" />
<br/>

<center>Phone number</center>
<input class="input" type="text" name="phone" value="{{phone}}" />
<br/>

<center>Email</center>
<input class="input" type="text" name="email" value="{{email}}" />
<br/>

</div>

<br/>
<br/>
<button type="submit">Save account details</button>

</form>
</div>
32 changes: 0 additions & 32 deletions views/account_details.ejs

This file was deleted.

24 changes: 24 additions & 0 deletions views/layout.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/public/css/screen.css' />
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div id="layout">
{{{body}}}
<div id="layout-footer"></div>
</div>
<div id="footer-wrap">

<div id="footer">
<center>
<a href="/public/about.html">about</a>
</center>
</div>
</div>
</body>
</html>

0 comments on commit 01c7957

Please sign in to comment.