Missing Function-Level Access Controls in Your Code

The most common mistake people make when implementing access control is misplacing or poorly implementing validation in the code. That means you don’t have access control right before the action that requires it. In this situation, attackers can circumvent access control by figuring out how the application handles the access checks.

For example, path validation mismanagement occurs when private functionality is hidden from unauthorized users on the client side, but no corresponding check is performed on the server side. An attacker who knows the application well enough would be able to access restricted functionality.

This example consists of a web application that builds a menu based on the user’s authentication status. Logged-out users see three links, whereas users with permission see four:

 // Function to build menu
 function​ getNav(req, cb) {
 var​ html = ​'<nav>'​ +
 '<a href="/page/1">Page 1</a> '​ +
 '<a href="/page/2">Page 2</a> '​ +
 '<a href="/page/3">Page 3</a> '​;
 
  req.session.can(​'read users'​, ​function​ (err, can){
 if​(!err && can) {
  html += ​'<a href="/users">Users</a>'​;
  }
  cb(​null​, html);
  });
 }
 
 // Show a welcome message
 app.get(​'/'​, ​function​ (req, res, next) {
  getNav(req, ​function​ (err, html) {
  res.send(html + ​'<br/><div>Welcome home</div>'​);
  });
 });

Let’s look at the functionality of the links:

 // Regular pages, show what page we are on
 app.get(​'/page/:nr'​, ​function​ (req, res, next){
  getNav(req, ​function​ (err, html) {
  res.send(html + ​'<div>Page '​ + req.params.nr +​'</div>'​);
  });
 });
 
 // Our admin function to show users
 app.get(​'/users'​, ​function​ (req, res, next) {
  getNav(req, ​function​ (err, html) {
  res.send(html + ​'<pre>'​ + JSON.stringify(db.users, ​''​, 2) +​'</pre>'​);
  });
 });

/page paths show different pages, but for admin accounts there’s also the /users path, which shows all the accounts of this website in JSON format. Do you see what’s wrong with this picture?

The problem is that the only authentication is in the construction of client-side HTML. But if the attacker knows (has seen, mapped, or otherwise gathered information) that there’s also the path /users meant for administrators, then the attacker can visit the path and see that part of the application. No authentication is required—only knowledge that it exists.

As you know by now, this isn’t a secure scenario. So let’s fix this by adding an access check to the function:

 // Our admin function to show users
 app.get(​'/users'​, ​function​ (req, res, next) {
 // Check access
  req.session.can(​'read users'​, ​function​ (err, can) {
 if​(err || !can) {
  res.sendStatus(403); ​// Send forbidden
 return​;
  }
  getNav(req, ​function​ (err, html) {
  res.send(html +
 '<pre>'​ + JSON.stringify(db.users, ​''​, 2) +​'</pre>'​);
  });
  });
 });

Now the path /users will return as unauthorized. The authorization check is done immediately before the execution, eliminating this attack vector. Depending on the nature of the application and the function, you can also add a bit of obfuscation to the mix:

 // Our admin function to show users
 app.get(​'/users'​, ​function​ (req, res, next) {
 // Check access
  req.session.can(​'read users'​, ​function​ (err, hasAccess) {
 if​(err || !can) {
  console.warn(​'Unauthorized access attempt'​, req.path, err);
  next(); ​// Move along
 return​;
  }
  getNav(req, ​function​ (err, html) {
  res.send(html +
 '<pre>'​ + JSON.stringify(db.users, ​''​, 2) +​'</pre>'​);
  });
  });
 });

By simply moving on to the next function, you refuse to even acknowledge the existence of this function—this prevents attackers from learning (or confirming) that there’s a privileged function at this path. At the same time, it can make development a hassle. Logging is added precisely for that reason—without it you get no feedback whatsoever as a developer. For a more thorough overview of why logging is important, you can review Decide What Gets Logged.

Pay Attention to Client-Side Forms

Paths aren’t the only client-side problems you have to worry about. It’s common for applications to validate forms on the client side, but that should never be the only data validation layer. Client-side validation does not substitute for server-side validation.

Security checks on the client side can be easily circumvented, such as by using a custom browser or proxy. Consider what happens if you have an application with a registration form and you’re a logged-in user (or administrator) who can add other administrator users. Unauthorized users don’t see this option on the form, but you do. The constructor would look something like the menu constructor you saw previously:

 // Show registration form
 app.get(​'/register'​, ​function​ (req, res, next) {
 var​ form = ​'<form method="POST">'​ +
 '<input type="text" name="username" placeholder="username" />'​ +
 '<input type="text" name="name" placeholder="name" />'​ +
 '<input type="text" name="company" placeholder="company" />'​;
 
 // If has rights then show admin checkbox
  req.session.can(​'add admins'​, ​function​ (err, has) {
 if​(!err && has) {
  form += ​'<label for="isAdmin">Is Admin? '​ +
 '<input id="isAdmin" type="checkbox" name="isAdmin" value="1" />'​ +
 '</label>'​;
  }
  form += ​'<input type="submit" value="Submit" />'​ +
 '</form>'​;
 
  res.send(form);
  });
 
 });

To complete the circle of bad access control, let’s add a form handler:

 // Post request handler
 app.post(​'/register'​, ​function​ (req, res, next){
 // Check username
 if​(db.users[req.body.username]) {
  res.sendStatus(409);
 return​;
  }
 
 var​ newUser = {
  name: req.body.name,
  company: req.body.company,
  isAdmin: req.body.isAdmin || 0 ​// if no isAdmin is sent then set to 0
  };
  db.users[req.body.username] = newUser;
 
  console.log(db.users); ​// show us the users
  res.redirect(​'/'​);
 });

We should now see some users listed in db.users after completing the registration form a few times:

 {
 'admin'​​:​ { name: ​'Admin'​, company​:​ ​'This'​, isAdmin​:​ 1 },
 'karl'​​:​ { name: ​'karl'​, company​:​ ​'Karl'​, isAdmin​:​ 0 }
 }

But since no check is performed on the data that was posted, an unauthorized user could also send the isAdmin field using a cURL request to modify the data in db.users:

 curl ​'http://localhost:3000/register'​ ​
 -H ​'Content-Type: application/x-www-form-urlencoded'​ ​
 --data ​'username=attack&name=attack&company=attack&isAdmin=1'

New users can be added to db.users, which is now a problem:

 {
 'admin'​​:​ { name: ​'Admin'​, company​:​ ​'This'​, isAdmin​:​ 1 },
 'attack'​​:​ { name: ​'attack'​, company​:​ ​'attack'​, isAdmin​:​ 1 }
 }

I think you see where we’re going with this. If you perform access checking right before the action, you prevent the unauthorized registrations:

 // Post request handler
 app.post(​'/register'​, ​function​ (req, res, next){
 
 // Check username
 if​(db.users[req.body.username]) {
  res.sendStatus(409);
 return​;
  }
 
 var​ newUser = {
  name: req.body.name,
  company: req.body.company,
  isAdmin: 0 ​// Default to false
  };
 
 // check if we are authorized to set the flag and set if allowed
  req.session.can(​'add admins'​, ​function​ (err, can) {
 if​(!err && can) {
  newUser.isAdmin = req.body.isAdmin || 0;
  }
 
  db.users[req.body.username] = newUser;
 
  console.log(db.users); ​// show us the users
  res.redirect(​'/'​);
  });
 });

Performing the access check right before the actual operation is the key to keeping the application secure.

Don’t Forget About Server-Side Validation

The most common server-side mistakes involve misconfigured access validation. It usually occurs when authentication is expected but it doesn’t happen or is forgotten. Let’s modify the last section’s registration form by adding a whole separate path. The form will be the same for both /register and /add-admin, but the handling is different. First we set up the routes:

 // Show registration form
 app.get(​'/register'​, ​function​ (req, res, next) {
  res.send(getForm());
 });
 
 // Post request handler for regular users
 app.post(​'/register'​, ​function​ (req, res, next){
  addUser(req.body, ​false​); ​// Add a regular user
  res.redirect(​'/'​);
 });
 
 // Authentication middleware
 app.get(​'*'​, easySession.can(​'add admin'​));
 
 // Show the admin user adding form
 app.get(​'/add-admin'​, ​function​ (req, res, next) {
  res.send(getForm(​'/add-admin'​));
 });
 
 // Post request handler for adding admin users
 app.post(​'/add-admin'​, ​function​ (req, res, next) {
  addUser(req.body, ​true​); ​// Add admin user
  res.redirect(​'/'​);
 });

When you visit the site you see only the /register link, which allows you to add a regular user. Even if you use the previous example’s cURL request, only a regular user is added. Additionally, even if the attacker knows that there is an /add-admin path, visiting it will show an Unauthorized error message.

Even though everything seems secure, it really isn’t.

Because we have misconfigured the authentication middleware, it doesn’t apply to POST requests, only GET. An attacker only has to change the action of the form, the POST path. This would let non-logged-in users add adminstrator users:

 curl ​'http://localhost:3000/add-admin'​ ​
 -H ​'Content-Type: application/x-www-form-urlencoded'​ ​
 --data ​'username=attack&name=attack&company=attack&isAdmin=1'

First, let’s fix the middleware registration to work for all requests, not just GET:

 // Authentication middleware
 app.all(​'*'​, easySession.can(​'add admin'​)); ​// all instead of get

You can also decide to use the middleware specifically before each route. The benefit of this approach is that you always see if authentication is there or not. The downside is that you’d have to write it every time:

 // Show the admin user adding form
 app.get(​'/add-admin'​, easySession.can(​'add admin'​), ​function​ (req, res, next) {
  res.send(getForm(​'/add-admin'​));
 });
 
 // Post request handler for adding admin users
 app.post(​'/add-admin'​, easySession.can(​'add admin'​), ​function​ (req, res, next) {
  addUser(req.body, ​true​); ​// Add admin user
  res.redirect(​'/'​);
 });

Always remember to do access validation, and always test the expected behavior to avoid mistakes associated with improperly placed validation. Don’t forget to perform server-side validation no matter how much validation exists on the client side. And the best place to perform authentication and validation is directly before the executable function or at the beginning of the function itself. This way, attackers can’t circumvent the authentication via some loophole.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.143.65