If you follow our social networks, it is very likely that you have come across some “Secure Coding Challenge” in order to identify a vulnerability in some code.
You can also listen to this article:
Knowing this, we separate in this article a step-by-step guide that can help you build a method to solve any of these challenges, in addition to adding knowledge for when performing the Code Review of your application.
According to the OWASP Code Review Guide, Code Review is a crucial process in the secure development cycle and is one of the best ways to identify vulnerabilities. Carried out by development teams, it can help mitigate potential exploits and vulnerabilities in code early in the cycle, contributing to the Shift-Left approach of DevSecOps.
In this sense, security challenges in code help developers practice problem-solving skills, better understand the programming language they use, and understand how attacks and vulnerabilities work.
Let’s go, to facilitate learning, we will use the following example of vulnerable code in Ruby on Rails:
Step 1: Know the language or framework used
1.1 Know the OWASP TOP 10 and the main risk categories and their vulnerabilities
Before starting to review the code, it is good to think about the most common vulnerabilities for this type of language or framework, in addition to thinking about the OWASP TOP 10 list of the main security risks.
Familiarizing yourself with the most common types of vulnerabilities will help you identify similar patterns in the source code. That is, knowing the programming language will help you identify vulnerable patterns more accurately.
1.2 Consult the language documentation
In this case, we are using Ruby on Rails. As the language document itself explains, Rails is a full-stack framework, being used in front-end and back-end web applications. Therefore, we can now consider security issues in web applications for our exercise.
1.3 See security topics in the language documentation
Then, we consulted the language documentation regarding the security issue. We found that there is no common pattern of vulnerabilities as it is a language with a very active community in problem solving.
However, the documentation draws attention to some common attack types for the framework: Session Hijacking, Cross-Site Request Forgery (CSRF), Injection.
Let’s pay attention to these types of attacks and what types of vulnerabilities are loopholes for them to happen, but let’s not limit ourselves to them. Here we start and think about the deductive method, establishing a major premise and creating relationships with a second proposition, called the minor premise.
Step 2: Understand the purpose of the code
Depending on the level of knowledge you have of the attack patterns, you may already be able to find the vulnerability of the code presented via deduction with the previous information. However, as the focus of this text is for beginner developers, let’s think about details.
2.1 Analyze the code data flow
There are some important concepts to keep in mind while reading the code and thinking from an attacker’s point of view, they are: sources, sinks, and data flow.
Sources can be a function that takes user input. Sink represents functions that execute system commands. After identifying the sources, we need to understand what the code would do with this data coming from the client.
If input from a malicious actor can go from source to sink in a path known as a data flow, or “data flow”, without filtering, proper validation or sanitization, then this flow could represent an Injection vulnerability.
In short, when doing this analysis, identify where the data ultimately ends up. Understanding, therefore, the functioning and construction of the data flow in code is essential to identify some of the most common vulnerabilities.
Let’s use the concept in practice with our case:
In Ruby, a module is a collection of methods, variables, and constants stored in a kind of container. It is similar to a class, but cannot be instantiated.
Resolvers are functions that the GraphQL server uses to fetch data for a specific query. Each field of your GraphQL types needs a corresponding solver function. When a query arrives at the backend, the server will call resolution functions that match the fields specified in the query.
Here we find a data flow in code with the Bookings class! This class is built through the ActiveRecord interface, an ORM library that defines the way data will be mapped between environments, how they will be accessed and recorded. This kind of code information you can get by looking up the syntax of the language in your documentation and community forums.
Within the class, the resolve method is defined having as parameter the variables linked to the database via ORM, in which by default all integers receive null value (nil) with the exception of the order string which is open via ‘ ‘ .
By standardizing the way the query is constructed in Return, it is possible to identify the SQL standard.
⚠️ The SQL database sorting functionality (ORDER BY) is a point of attention, as it is responsible for selecting a column or a number and ordering the result according to the values contained in this column. As the order argument in question is connected to the GraphQL server, it is important to be aware of how this connection will be constructed.
In the middle of this flow, he defines some important logical conditions to understand the return of this method that we will see in the next subtopic.
2.2 Analyze the code control flow
With the idea of data flow in mind, another perspective that can help you identify problems in your code is to analyze logical conditions. This means examining a function and identifying various branch conditions such as loops, if statements, try/catch blocks, etc.
By understanding the logical part of the code, you can find out under what circumstances and conditions each branch runs.
In practice with our example:
The arguments used as parameters of the resolve method are, via the splat (*) operator, sent to each function as an individual parameter.
⚠️In general, in the order field, a value is defined for the object in the condition that it is empty, but attention, its open receipt value right in the method definition can bring complications here if the input value is manipulated.
In bookings, there is a relationship with the definition of the access user without applying specific conditions, but applying relationships with the location in the database via where. Then we get what the method returns:
When reading the Ruby code several times, it becomes clear that its purpose is to perform ordered queries. Now, is it possible to change that purpose? Let’s go to the last step: the construction of hypotheses.
Step 3: Do Hypothesis Tests with a Hacker Mindset
Now that you really know the purpose of the code, try to think “outside the box” and raise possible hypotheses, subverting the original purpose of the code. We speak here of a hacker mentality, in the sense of looking at dynamics and processes differently from the conventional.
3.1 Be pessimistic and imagine the worst
With the code’s purpose in mind, consider:
- What is the worst possible scenario?
- What is the probability of the worst case scenario happening?
- How can the code generate this scenario?
“Thinking like a hacker” requires you to look at systems differently, this means looking at the obvious, understanding the possibility of human error, and taking a sort of chain-de-hope-dealing approach to achieving your goal.
3.2 Subvert the order through hypotheses
Now that you have the worst-case scenarios in perspective, do some thinking about the code and build hypotheses through questions like:
- Can I change the given input and the expected output?
- Is there any validation for the input data? If so, is it possible to subvert it?
- Are there any restrictions or limitations for entry?
- Is it possible to change the data type of the given input?
- Does the program transfer confidential data without encryption?
- Is the encryption method susceptible to attack?
- If the program uses random strings, are they random enough?
Let’s go back to our initial exercise. So far, we’ve been able to identify several important pieces of information about the code.
It uses Ruby on Rails, being one of the main attacks for applications in this language: Session Hijacking, Cross-Site Request Forgery (CSRF) and Injection, also common for web applications according to OWASP TOP 10 (Step 1).
The code is intended to perform ordered queries with a data flow demonstrated by the Booking class, which defines the pattern of receiving data through the resolve method. This method has the order parameter as regards the “ascending or descending” ordering of the SQL database query performed (Step 2).
Reflecting on hypotheses (Step 3), the worst scenario would be for an external agent to have access or control to all data in this application and the chances of this happening depend on the way the query is constructed with the database. Therefore, the code can contribute to this scenario in the way it builds the queries and, in the case of our exercise, in the way it receives the order from the client, which is the only “door” we identified.
Now, let’s subvert the purpose of the code and test a single hypothesis:
- If someone performs some injection in the order field with a string “1 ASC — 1 DESC — DESC” it will result in the order of the results being reversed.
If you look closely at the code, you will see that there is no filter or proper validation from source to sink in this data stream. The most likely result will be the reorganization of the results in descending order, confirming our hypothesis of subversion of the code.
Okay, we identified the vulnerability of the challenge, it was a SQL Injection.
Skills that can improve with practice!
Practice leads to perfection. The more coding challenges you do, the more skillful you will be, as well as building new methods that will add value to your Code Review.
Start with the easiest challenges. Then go back to the challenges you completed and try to solve them differently or solve similar challenges. However, finding problems is only half the battle. Developers must also know how to fix vulnerabilities and, more importantly, prevent their recurrence.
Participating in coding challenges will help you to point out errors in lines of code easily. In addition, it will empower your technical skills to write error-free code. On our Twitter you can find monthly challenges and participate in discussions with security experts.
At Conviso Platform’s People and Culture, we take a different approach to secure coding training. We teach developers how to identify a security issue, explore it on their own, and then fix it by modifying the code responsible for that issue. Practical how to solve a challenge!