Misplaced trust in the client by itself is a very general and broad topic. However, believe it or not, we already covered some aspects of this topic in the previous chapters.
Misplaced trust in the client generally means that if we, as developers, are overly trusting, especially in terms of how our JavaScript will run in the client or if there is any input from the users, we might just set ourselves up for security flaws.
In short, we cannot simply assume that the JavaScript code will run as intended.
In general, while we try our best to write secure JavaScript code, we must recognize that the JavaScript code that we write will eventually be sent to a browser. With the existence of XSS/CSRF, code on the browser can be manipulated fairly easily, as you saw in the previous chapter.
We will start off with a simple application, where we attempt to create a user, similar to many of the apps we are familiar with, albeit a more simplified one.
We will walk through the creation of the app, use it, and then utilize it again under modified circumstances where the trust actually gets misplaced.
This example is based on Tornado/Python. You can easily recreate this example using Express.js/Node.js. The important things to note here are the issues happening on the client side.
What we are going to code in this section is a simple user creation form, which sends the values to the backend/server side. On the client side, we are going to use JavaScript to prevent users from creating usernames with the a
character and passwords containing the s
character.
This is typical of many forms we see: we may want to prevent the user from creating input using certain characters.
As usual, if the user's input satisfies our requirements, our JavaScript code will enable the Submit button, enabling the user to create a new user.
With that in mind, let's start coding. To start off, create the following directory structure:
mistrust/ templates/ mistrust.html mistrust.py
Since this example is based on Tornado/Python and it has only one functionality (that of creating a user), this is a fairly straightforward piece of code. Open your editor and name a new file mistrust.py
. The code is as follows:
import os.path import re import torndb import tornado.auth import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web import unicodedata import json from tornado.options import define, options define("port", default=8000, help="run on the given port", type=int) class Application(tornado.web.Application): def __init__(self): handlers = [ (r"/", FormHandler) ] settings = dict( blog_title=u"Mistrust", template_path=os.path.join(os.path.dirname(__file__), "templates"), xsrf_cookies=False, debug=True ) tornado.web.Application.__init__(self, handlers, **settings) class FormHandler(tornado.web.RequestHandler): def get(self): self.render("mistrust.html") def post(self): print self.get_argument('username') print self.get_argument('password') data = { 'success':True } self.write(data) def main(): tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(Application()) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() if __name__ == "__main__": main()
Basically, we have only one handler, FormHandler
, which shows the form when we hit the /
URL. The POST
function simply receives the username and password. Presumably, we can save this information for our new user.
Next, let's work on the client-side code. Create a new file in the templates/
folder and name it mistrust.html
. As usual, we start with a basic Bootstrap 3 template, which is as follows:
<!DOCTYPE html> <html lang="en"> <head> <title>Mistrust Example</title> <!-- Bootstrap core CSS --> <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"> <style> #username-error, #password-error { color:red; } #success-msg, #fail-msg { display:none; } </style> </head> <body> <div class="container"> <div class="header"> <ul class="nav nav-pills pull-right"> <li class="active"><a href="#">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Contact</a></li> </ul> <h3 class="text-muted">Mistrust Example</h3> </div> <div class="jumbotron"> <h1>Create User</h1> <div id="success-msg" class="alert alert-success" role="alert">Success</div> <div id="fail-msg" class="alert alert-danger" role="alert">Oops, something went wrong</div> <div role="form"> <div class="form-group"> <label for="username">User Name </label><span id="username-error"></span> <input type="text" class="form-control" id="username"> </div> <div class="form-group"> <label for="password">Password </label><span id="password-error"></span> <input type="password" class="form-control" id="password"> </div> <button id="send" type="submit" class="btn btn-success" disabled>Submit</button> </div> </div> <div class="footer"> <p>© Company 2014</p> </div> </div> <!-- /container --> <!-- Bootstrap core JavaScript <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> </body> </html>
There is nothing special about this piece of code. It is simply a HTML template showing a form.
Next, insert the following JavaScript code beneath <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
:
<script> var okUsername = null; var okPassword = null; function checkUserNameValues() { var values = $('#username').val(); if (values.indexOf("s") < 0) { okUsername = true; $('#username-error').html(""); } else { okUsername = false; $('#username-error').html("Not allowed to use character 's' in your password"); } if (okUsername === true && okPassword === true) { $('#send').prop('disabled', false); } } function checkPasswordValues() { var values = $('#password').val(); if (values.indexOf("a") < 0) { okPassword = true; $('#password-error').html(""); } else { okPassword = false; $('#password-error').html("Not allowed to use character 'a' in your password"); } if (okUsername === true && okPassword === true) { $('#send').prop('disabled', false); } } function formEnter() { var a = $('#username').keyup(checkUserNameValues); var b = $('#password').keyup(checkPasswordValues); } // here will do the form post and simple validation function submitForm() { // here I will check for "wrong" stuff if (ok_username === true && ok_password === true) { // go ahead and post to ajax backend var username = $("#username").val(); var password = $("#password").val() var request = $.ajax({ url: "/", type: "POST", data: { username : username, password:password }, dataType: "json" }); request.done(function( response ) { if(response.success == true) { $( "#success-msg" ).show(); } else { $("#fail-msg").show(); } }); request.fail(function( jqXHR, textStatus ) { $("#fail-msg").show(); }); } else { alert("Please check your error messages"); } // enables or disables the button return; } $('document').ready(function() { // so here I will do the form posting. formEnter(); $("#send").click(submitForm); }) </script>
We have four major functions in this piece of JavaScript code, which are discussed as follows:
checkUserNameValues()
: This function checks whether the username is valid or not. For our purposes, it must not contain the s
character. If it does, we will show an error message at the #username-error
element.checkPasswordValues()
: This function checks whether the password is valid or not. In this case, it is checking whether the password contains the s
character or not. If it does, it will show an error message in #password-error
.formEnter()
: This function simply calls checkUserNameValues()
and checkPasswordValues()
whenever there is a keyup
event when the user is in the process of entering their username or password.submitForm()
: This function submits the form if the user input adheres to our rules, or it returns a fail message at the #fail-msg
element.Now that we have coded the app, save everything and change directory to the root of the application. Issue the following command:
python mistrust.py
There is no need to use any database for this app. After you have issued this command, go to http://localhost:8000
; you should see the following output:
Now you can test the app. Enter your username and password. If you have entered something illegal, this is what you will see:
Note the error messages beside the User Name and Password fields.
On the other hand, should you enter the credentials correctly, you will receive a successful message, as shown in the following screenshot:
Now that we have made sure our code is working correctly, it's time to manipulate the code to show that we, as developers, should never trust the client.
You need to perform the following steps to manipulate the JavaScript code:
asd
and asd
for both your username and password, both of which are illegal under our rules. Going back to your developer tool, head straight to console, and type the following:ok_password = true ok_username = true
Presto! It succeeds!
In my server, I receive both values: asd
and asd
, as shown in the following screenshot:
Weird isn't it?
Actually, it is not. Remember that the JavaScript code we write is sent to the client side, which means that it is free for all (malicious developers?) to manipulate. Look how much harm my simple technique can possibly cause: simply using Google's developer tools, I side-stepped the basic requirements of not using the s
character and the a
character for my username and password respectively.
For our particular example, we could have done something to prevent this from happening. And that is to include server-side checking as well. Now, feel free to check the code in mistrust2.py
, and look for FormHandler
. The post()
function has now been changed, as follows:
class FormHandler(tornado.web.RequestHandler): def get(self): self.render("mistrust.html") def post(self): username = self.get_argument('username') password = self.get_argument('password') # this time round we simply assume false data = { 'success':False } if 's' in username: self.write(data) elif 'a' in password: self.write(data) else: data = { 'success':True } self.write(data)
We simply look for illegal characters when accepting the username and password. Should they contain any illegal characters, we simply return a failed message.
3.12.163.175