In one line, this project can be explained as “Secure Coding Practices by Developers for Developers“. OWASP ProActive Controls is a document prepared for developers who are developing or are new to developing software/application with secure software development. This OWASP project lists 10 controls that can help a developer implement secure coding and better security inside the application while it is being developed. Following these secure application development controls ensures that the key areas of the development cycle have secure coding along with traditional coding practices. The strength of this project is not just in the listed 10 controls but in the key references associated with it. Every control extends the knowledge and capabilities by mentioning existing OWASP or other open source projects that can be used to strengthen the security of an application. The ten controls defined by this project are:
Parameterize Queries
Encode Data
Validate All Inputs
Implement Appropriate Access Controls
Establish Identity and Access Controls
Protect Data and Privacy
Implement Logging, Error Handling and Intrusion Detection
Leverage Security Features of Frameworks and Security Libraries
Include Security-Specific Requirements
Design and Architect Security In
Let us go deeper into each ProActive Control and see what it takes for us to implement it in the real world.
PARAMETERIZE QUERIES
One of the most dangerous attacks on a Web application and its backend data storage is SQL injection. It occurs when a user sends malicious data to an interpreter as an SQL query, which then manipulates the backend SQL statement. It is easy for an attacker to find a SQLi vulnerability using automated tools like SQLMap or by manual testing. The simplest and most popular attack vector used is:
1′ or ‘1’= ‘1
Submitting it as a username and password or in any other field can lead to an authentication bypass in many cases.
Here is an example of typical SQL injection in a user authentication module:
[sql] String username= request.getParameter(“username”); String password= request.getParameter(“password”); Class.forName(“com.mysql.jdbc.Driver”); Connection con = (Connection) DriverManager.getConnection(“jdbc:mysql://database-server:3306/securitydb:”, “root” ,“root”); Statement st= con.createStatement(); ResultSet rs=st.executeQuery(“select * from users where username=’"+username+"’ and password=’"+password+"’ limit 0,1”); [/sql]
In this vulnerable code, the ‘Statement’ class is used to create a SQL statement, and at the same time it is modified by directly adding user input to it, then it is executed to fetch results from the database. Performing a simple SQLi attack in the username field will manipulate the SQL query, and an authentication bypass can take place.
To stop a SQLi vulnerability, developers must prevent untrusted input from being interpreted as a part of a SQL query. It will lead to an attacker not being able to manipulate the SQL logic implemented on the server side. OWASP ProActive Controls recommends that developers should use parameterized queries only in combination with input validation when dealing with database operations.
Here is an example of SQL query parameterization:
[sql] String username=request.getParameter(“username”); String password=request.getParameter(“password”); Class.forName(“com.mysql.jdbc.Driver”); Connection con=( Connection) DriverManager.getConnection(“jdbc:mysql://database-server:3306/securitydb:”, “root” ,“root”); PreparedStatement ps=(PreparedStatement) con.prepareStatement(“select * from users where username=? and password=? limit 0,1”); ps.setString(1,username); ps.setString(2,password); ResultSet rs=ps.executeQuery(); if(rs.next()) out.println(“Login success”); else out.println(“Login failed”); [/sql]
Using a parameterized query makes sure that the SQL logic is defined first and locked. Then the user input is added to it where it is needed, but treated as a particular data type string, integer, etc. as whole. In a database operation with a parameterized query in the backend, an attacker has no way to manipulate the SQL logic, leading to no SQL injection and database compromise.
SQL injection vulnerability has been found and exploited in applications of very popular vendors like Yahoo! too.
ENCODE DATA
Data encoding helps to protect a user from different types of attacks like injection and XSS. Cross Site Scripting (XSS) is the most popular and common vulnerability in Web applications of smallest to biggest vendors with a Web presence or in their products. Web applications take user input and use it for further processing and storing in the database when ever needed. Also user input could be part of the HTTP response sent back to the user. Developers should always treat user input data as untrusted data. If user input at any point of time will be part of the response to user, then it should be encoded. If proper output encoding has been implemented, then even if malicious input was sent, it will not be executed and will be shown as plain text on the client side. It will help to solve a major web application vulnerability like XSS.
Here is an example of XSS vulnerability:
[sql] if(request.getMethod().equalsIgnoreCase(“post”)) { String name = request.getParameter(“name”); if(!name.isEmpty()) { out.println("
Hi “+name+”. How are you?"); } } [/sql]
In the above code, user input is not filtered and used, as it is part of message to be displayed to the user without implementing any sort of output encoding.
Most common XSS vulnerabilities that affect users and are found in applications are of two types:
Stored XSS Reflected XSS
Stored XSS are those XSS which get stored on a sever like in a SQL database. Some part of the application fetches that information from the database and sends it to the user without properly encoding it. It then leads to malicious code being executed by the browser on the client side. Stored XSS can be carried out in public forums to conduct mass user exploitation.
In Reflected XSS, the XSS script does not get stored on the server but can be executed by the browser. These attacks are delivered to victims via common communication mediums like e-mail or some other public website.
By converting input data into its encoded form, this problem can be solved, and client side code execution can be prevented.
Here is an example of output encoding of user input:
[sql] if(request.getMethod().equalsIgnoreCase(“post”)) { String name = StringEscapeUtils.escapeHtml(request.getParameter(“name”)); if(!name.isEmpty()) { out.println("
Hi “+name+”. How are you?"); } } [/sql]
In the next section you will see how input validation can secure an application. Combining input validation with data encoding can solve many problems of malicious input and safeguard the application and its users from attackers.
OWASP has a project named OWASP ESAPI, which allows users to handle data in a secure manner using industry tested libraries and security functions.
VALIDATE ALL INPUTS
One of the most important ways to build a secure web application is to restrict what type of input a user is allowed to submit. This can be done by implementing input validation. Input validation means validating what type of input is acceptable and what is not. Input validation is important because it restricts the user to submit data in a particular format only, no other format is acceptable. This is beneficial to an application, because a valid input cannot contain malicious data and can be further processed easily.
Important and common fields in a web application which require input validation are: First Name, Last Name, Phone Number, Email Address, City, Country and Gender. These fields have a particular format which has to be followed, especially email and phone number, which is very common.
It is a known fact that first name and last name cannot have numbers in them; you cannot have a name as John39 *Bri@n. Such user input is treated as malicious and thus requires input validation.
Input validation can be implemented on client side using JavaScript and on the server side using any server side language like Java, PHP etc. Implementing server side input validation is compulsory, whereas client side is optional but good to have.
Now input validation is again of two types:
Blacklist Whitelist
The simplest example to explain the two can be: A security guard stops all guys wearing a red t-shirt who are trying to enter a mall, but anyone else can enter. This is a blacklist, because we are saying the red color is blocked. Whereas a whitelist says that guys wearing white, black and yellow t-shirt are allowed, and the rest all are denied entry. Similarly in programming, we define for a field what type of input and format it can have. Everything else is invalid. It is called whitelisting. Blacklisting is invalidating an input by looking for specific things only. For example, specifying that a phone number should be of 10 digits with only numbers is whitelist. Searching input for A-Z and then saying it is valid or not is blacklisting, because we are invalidating using alphabet characters only. Blacklisting has been proven to be weaker than whitelisting. In the above case, if a user enters 123456+890, then a blacklist will say it is valid because it does not contain A-Z. But it is wrong. Whereas a whitelist will say it contains a character that is not a number, and only numbers are allowed, so it is invalid. Input validation can be implemented in a web application using regular expressions. A regular expression is an object that describes a pattern of characters. These are used to perform pattern based matching on input data. Here is the example of a regular expression for first name: [sql] ^[a-zA-Z ]{3,30}$ [/sql] This regular expression ensures that first name should include characters A-Z and a-z. The size of first name should be limited to 3-30 characters only. Let’s take another example of regular expression for username: [sql] ^[a-z0-9_]{3,16}$ [/sql] Here this expression shows that username should include alphabets ‘a-z’, numbers ‘0-9’ and special characters underscore ‘’ only. The input length should be limited to 3-16 only. Email address validation can be performed using the following regular expression: [sql] ^[a-zA-Z0-9.!#$%&’*+/=?^`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$ [/sql] Depending upon the programming language a developer uses to build an application, regular expression can easily be implemented in it. Another advantage of regular expressions is that there are many industry tested regular expressions for all popular input types. So you don’t have to write one from scratch and then get it security tested. It is better to use industry tested regular expressions than writing one on your own (which in most cases will be flawed). OWASP has an Input Validation Cheat Sheet to help you implement proper input validation in your application. IMPLEMENT APPROPRIATE ACCESS CONTROLS Before we begin, it should be crystal clear that authentication is not same as authorization. Authentication takes care of your identity, whereas authorization makes sure that you have the authority or privilege to access a resource like data or some sensitive information. A simple real world example to show this can be: Alice visits Bob’s home. Her identity is known to Bob, so he allows her to enter her home (if she was not known to Bob then entry would have been denied, aka authentication failure). Alice is now inside Bob’s home. But she cannot open Bob’s family safe at home, because she is not authorized to do so. On the other hand, Bob’s sister Eve is known, so successful authentication occurs, and she is a family member, so she is authorized to access the family safe, aka successful authorization. Implementing authorization is one of the key components of application development. It has to be ensured at all times that access certain parts of the application should be accessible to users with certain privileges only. Authorization is the process of giving someone permission to do or have something. It is to be noted again that authentication is not equivalent to authorization. Many developers have a tough time handling authorization, and at some point leave a gap that gets exploited, leading to unauthorized data access. To solve this problem, access control or authorization checks should always be centralized. All user requests to access some page or database or any information should pass through the central access control check only. Access control checks should not be implemented at different locations in different application codes. If at any point in time you have to modify an access control check, then you will have to change it at multiple locations, which is not feasible for large applications. Access control should by default deny all requests which are from a user for a resource for which either access is restricted or an authorized entry has not been made. Layered Authorization Checks should be implemented. It means that the user’s request should be checked for authorization in layered manner instead of a haphazard manner. Below is an example:
User requests “/protected” file access.
Is user logged-in?
Is user normal user or privileged user?
Is user allowed access to the resource?
Is resource marked as locked?
If the access control check at any point in 1-5 fails, then the user will be denied access to the requested resource.
OWASP Access Control Cheat Sheet can prove to be good resource for implementing access control in an application. ESTABLISH IDENTITY AND AUTHENTICATION CONTROLS Authentication is the process by which it is verified that someone is who they claim to be, or we can say it is the process of identifying individuals. Authentication is performed by entering username or password or any sensitive information. Authentication and identity are two components of accessing any kind of information that goes hand-in-hand. For example, if you want to access your bank account details or perform a transaction, you need to login into your bank account website. Successfully authenticating to your bank account proves that you are the owner of that account. From this discussion, it is clear that username and password are the elements of authentication that prove your identity. OWASP ProActive: Establish Identity and Authentication Controls says that all the modules of an application which are related to authentication and identity management should have proper security in place and secure all sensitive information. Also, an application should request for and store only the information which is absolutely needed, and nothing else. Sensitive information like password and account number should be either stored in encrypted or hashed format inside a database, so that it cannot be misused by a malicious user if he or she gains unauthorized access and decrypts it easily. Below is an example of an application that stores the user’s password in plaintext inside a MySQL database. [sql] String username=request.getParameter(“username”); String password=request.getParameter(“password”); PreparedStatement ps = (PreparedStatement) con.prepareStatement(“insert into login_users values(?,?)”); ps.setString(1,username); ps.setString(2,password); [/sql] Here the password is stored in plain text. If the database is compromised at the same time, the attacker will be able to access the user account easily. The attacker will be able to login to the user’s account using the username and password from the database, which is stored in plain text. But this vulnerability can be exploited by converting sensitive information into a hashed format, like in salted MD5 or SHA2 hash format or in encrypted form. Here is an example of hashing sensitive information before storing it in a SQL database: [sql] String username=request.getParameter(“username”); String password=request.getParameter(“password”); MessageDigest m = MessageDigest.getInstance(“MD5”); m.update(s.getBytes(),0,s.length()); String calc_hash = new BigInteger(1,m.digest()).toString(16); if(calc_hash.length()<32) { calc_hash = “0”+calc_hash; } PreparedStatement ps = (PreparedStatement) con.prepareStatement(“insert into login_users values(?,?,?)”); ps.setString(1,username); ps.setString(2,password); ps.setString(3,calc_hash); [/sql] The above code shows that here sensitive information (i.e. password) is stored in a salted MD5 format. The salt is different for every new registration. If the database is compromised, then the attacker will have to find clear text for the hashed passwords, or else it will be of no use. Broken Session Management is also a type of vulnerability which exists in a web application that does not properly implement session management. For example, if a user logs out from his/her account, but he/she is redirected to some page, but session is not invalidated properly, a post-login page is opened without asking for re-authentication. Another example can be a session cookie for pre- and post-login being same. Vulnerable code: [sql] String username = request.getParameter(“username”); String password = request.getParameter(“password”); PreparedStatement ps=(PreparedStatement) con.prepareStatement(“select * from users where username=? and password=? limit 0,1”); ps.setString(1,username); ps.setString(2,password); ResultSet rs=ps.executeQuery(); if(rs.next()) { session.setAttribute(“useracc”, rs.getString(“username”)); out.println(“Login success”); } else { out.println(“Login failed”); } [/sql] Observe in the above code that the session cookie JSESSIONID remains the same for pre- and post-login. This vulnerability can be exploited by an attacker who has physical access to the machine and notes the value of session cookie pre-authentication. This attack is known as Session Fixation. This patched code will invalidate the session when authentication is successful and creates a new session cookie value. This changes the post-login session cookie value, and Session Fixation vulnerability cannot be exploited. [sql] String username=request.getParameter(“username”); String password=request.getParameter(“password”); PreparedStatement ps=(PreparedStatement) con.prepareStatement(“select * from users where username=? and password=? limit 0,1”); ps.setString(1,username); ps.setString(2,password); ResultSet rs=ps.executeQuery(); if(rs.next()) { session.invalidate(); request.getSession(true); session.setAttribute(“useracc”, rs.getString(“username”)); out.println(“Login success”); } else { out.println(“Login failed”); } [/sql] The session cookie value should never be predictable, and should comply with strong complexity for better security. Authentication and secure storage is not just limited to the username-password module of an application. Other key modules like forgot password and change password are also part of authentication. Financial data and personal information like SSN are some of the most important details a person is concerned with, so an application storing that data should make sure it is encrypted securely. OWASP has some key resources like: Authentication Cheat Sheet Session Management Cheat Sheet In this part of OWASP ProActive Controls, we discussed in depth how ProActive Controls 1-5 can be used in an application as a secure coding practice to safeguard it from well-known attacks. The controls discussed do not modify application development lifecycle, but ensure that application security is given the same priority as other tasks and can be carried out easily by developers. We will see the last 5 ProActive Controls in the next and final part. Reference: https://www.owasp.org/index.php/OWASP_Proactive_Controls