Dev Overflow – Part 2
By Mikhail Sudakov, Cyber Security Architect and Analyst, LEO Cyber Security.
Like all areas of security, software security is a state of mind and a way of thinking. It should not be a goal to be achieved or a checkbox to be checked. Developers must weave it into their software from the very beginning, or they will regret it.
A1 – Injection
“Injection flaws, such as SQL, NoSQL, OS, and LDAP injection, occur when untrusted data is sent to an interpreter as part of a command or query” – (OWASP). As mentioned before, machines are powerful… but dumb. Injection vulnerabilities arise when the computer confuses instructions with data and is instructed to execute the latter. In fact, many software vulnerabilities are caused by that simple confusion! Computers are dumb – they simply don’t know any better. It’s up to the programmer to ensure that the machine cannot get confused.
Consider the following query for instance:
... String userID = Request.QueryString["id"]; String sqlQuery = "SELECT * FROM user WHERE user_id = '" + userID + "';"; ...
In case you didn’t know, every time you use “SELECT *”, an adorable and fluffy baby bunny dies somewhere. In all seriousness, do you really need to fetch every single field to retrieve the data? Please stop the “bunny-cide” and just select what you need. Although this doesn’t immediately have to do with security, I believe it is related and describes the issue in the simplest of terms. If you embrace security as a state of mind, you will think of instructions differently. For example, only fetch and store what you are going to use and give a process or a user the least amount of privileges required to complete a task. So stop killing bunnies!
Now to the main problem… What if the “id” variable comes through as follows?:
' OR 1 = 1; --
The “- -” is meant to indicate a comment that, depending on the database engine, may be “#” or something else as well. Let’s see how that affects the original query:
SELECT * FROM user WHERE user_id = '' OR 1 = 1; --';
In the simplest of mathematical terms, 1 always equals 1 so that results in the TRUE condition. Logically, anything <OR> TRUE is TRUE, unconditionally and completely. I can say “I am President George Washington or 2 * 2 is 4”, and that entire statement is TRUE, even though the first part is FALSE! FALSE <OR> TRUE is TRUE.
In effect, what the final (injected) query does is fetch every record from the user table where the condition of “1 = 1” holds true, which is every record – period. Really, we have just shot ourselves in the foot… with a bazooka! This is one of the simplest examples of SQL injection. Notice how our use of the comment (“- -“) renders everything that comes after it useless as the interpreter disregards it all. Thus, the query does not break and would execute successfully, granting us data that we were not supposed to have.
The adversary confused the machine to execute a part of the query that was originally supposed to contain data (and we never want to execute data or bad things will happen!). It so happens that there is an elementary way to separate instructions from data and prevent the vast majority of all SQL injection attempts. Depending on your framework, the syntax may vary, but the semantics are the same:
- Compile the query in its entirety with placeholders (parameters) for data,
- Instruct the machine to execute the compiled query as is. That is, no matter what will later come in those data parameters, the computer is under orders to execute an already-compiled query. That order cannot be countermanded no matter what comes in the parameters, injection or benign data, and
- Append the expected parameters and complete execution.
It might look like this:
... String userID = Request.QueryString["id"]; String sqlQuery = "SELECT * FROM user WHERE user_id = @uid;"; var sqlCmd = new SqlCommand(sqlQuery); sqlCmd.Parameters.Add("@uid", SqlDbType.VarChar).Value = userID; ...
Now, if the same injection of ” ‘ OR 1 = 1; – – ” is passed into this query, it will attempt to fetch a user whose ID is literally ” ‘ OR 1 = 1; – – ” and return empty-handed. This approach is known as “query parameterization” or “query pre-compilation”. Note that while this approach will stop most injection attacks cold, it will not prevent all such attacks; for instance, stored procedures may still be vulnerable. Please consult the OWASP Top 10 cheat sheet for a more complete list of countermeasures you can employ.
This principle of corrupting instructions by allowing execution of passed data is a flaw that appears in numerous software vulnerabilities, such as the infamous buffer overflow. It is absolutely imperative for developers to never, under any circumstances, permit any such confusion of instructions and data. The logic must be ironclad to prevent potential data execution. Failure to do that will bring with it truly terrible consequences. Programmer beware!
The next post will focus on OWASP A2 – Broken Authentication.
Mikhail is a cybersecurity professional with an enormous passion for the field and thirst for knowledge. In addition to his day job, Mikhail is a professor of cryptology at St. Bonaventure University. A programmer specializing in software security and cryptology, Mikhail always seeks to expand his horizons, hone his skills, and take on new challenges. One day, he hopes to know enough to realize that he knows nothing. Mikhail holds an Offensive Security certification (OSCP) and, although he is a “Russian hacker”, his hat is white and his directive is to protect.