This is the second article in the OWASP Top 10 Series
INTRO
Injection is the number 1 risk of the Top 10 (In fact, it has been so for very long time). At time of this writing (May 2019) the likelihood of sql injection has dropped to 5% (from the 20% at the end of 2007), but the impact is so severe that Injection is still the first one of the OWASP Top 10. It´s important to note that when referring to injection we are not only talking about SQL Injection, applications can have injections over any queryable system like NoSQL Injection, XPath Injection, Command Injection, LDAP Injection, etc… Once vulnerability has been discovered, the exploitability can be really easy; in fact, there exists automated tools that allow passing one URL and extracting info from the system.
Patterns and frameworks like Entity Framework and .NET are evolved to provide safer ways to access your data providing in line parametrization and other patterns, but vulnerabilities still remains. Even more, the arrival of NoSQL has introduced new forms of injections, which is a real and dangerous step backwards. In words of Andrew van der Stock (from OWASP team): “if it wasn´t for NoSQL, Injection would be the number 4”
In addition to native defences and protection patterns, we can also try to mitigate injection vulnerabilities by using SAST (Static Application Security Testing) which can help us to detect vulnerable injection patterns, but their efficiency is far from modern IASTs (which is more recommended).
Injection Explained
From a general perspective injection arises when untrusted data is provided to an interpreter as part of a query or command. The attacker’s data can trick the interpreter for executing unintended commands. This way, the exploitability is really high because almost any source of data becomes into a potential injection vector. Injection vulnerabilities are also prevalent, according to owasp top 10 document:
As a general rule injection flaws are easy to discover when examining code, so SASTs can help here to mitigate injection vulnerabilities, (but keep in mind that using an IAST is a more reliable option). The technical impact is severe and can result in data loos or corruption, lack of accountability, or even denial service.
Untrusted Data
In order get into injection details, let´s first review concept of untrusted data for web applications, for instance let´s suppose we have following URL ASP.NET Web Forms .NET application
From a very simple perspective, the problem is that urls of this form sometimes translates to database sentences like
Select * from Persons where PersonName = ‘Santiago’ |
This way, if security actions are not taken, untrusted data can be propagated to database query.
So, if we focus into the previous url we can identify two parts: trusted and untrusted data. In general untrusted data is that entry info for which the integrity is no verifiable. This data may include dangerous contents like: SQL Injection, XSS or even binaries with malware
In terms of sources of untrusted data we can enumerate the following:
- From the user
- In body: Posted via form
- In url: Routed-Params (ASP.NET MVC), querystring
- From the browser
- In Request Header
- In Cookies
- From external locations
- Your own database (Store XSS)
- External services
So, as we have seen, many potential sources of injection when considering web applications.
Sample: Untrusted Data at Body for ASP.NET MVC Application
Let´s consider a very basic example, an ASP.NET MVC application in which untrusted data goes into form fields. This application will show a simple search person page in which person records are searched by defining their names in a text field, something like
At Controller:
public ActionResult SearchByNameWeak(string name)
{
if (string.IsNullOrWhiteSpace(name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
else
{
var persons = _helper.GetPersonsByName(name);
if (persons != null && persons.Count() > 0)
return View("PersonResults", persons);
else
return HttpNotFound();
}
}
At Helper
public List<Person> GetPersonsByName(string name)
{
try
{
List<Person> results = new List<Person>();
DataTable dataTable = new DataTable();
// Vulnerable concat !!
string query = "select * from [Persons] where Name like '" + name + "'";
dataTable =ExecuteQueryReader(query);
if (null != dataTable.Rows && dataTable.Rows.Count > 0)
{
foreach (DataRow dr in dataTable.Rows)
{
Person person = new Person();
person.Id = Guid.Parse(dr["Id"].ToString());
person.Name = dr["Name"].ToString();
person.Surname = dr["Surname"].ToString();
person.Email = dr["Email"].ToString();
person.DateOfBirth = System.Convert.ToDateTime(dr["DateOfBirth"]);
person.Details = dr["Details"].ToString();
person.Gender = (dr["Gender"].ToString().TrimEnd() == "Male" ? Gender.Male : Gender.Female);
person.FlattedAddress = dr["FlattedAddress"].ToString();
person.Mobile = dr["Mobile"].ToString();
person.Phone = dr["Phone"].ToString();
person.PostalCode = dr["PostalCode"].ToString();
person.NIF = dr["NIF"].ToString();
person.Married = System.Convert.ToBoolean(dr["Married"].ToString());
results.Add(person);
}
}
return results;
}
catch (Exception ex)
{
// implement your exception handing here
throw;
}
}
public DataTable ExecuteQueryReader(String query)
{
SqlCommand myCommand = new SqlCommand();
DataTable dataTable = new DataTable();
DataSet ds = new DataSet();
try
{
myCommand.Connection = OpenConnection();
myCommand.CommandText = query;
using (SqlDataReader dataReader = myCommand.ExecuteReader())
{
dataTable.Load(dataReader);
}
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeSelectQuery - Query: " + query + " \nException: " + e.StackTrace.ToString());
throw;
}
finally
{
CloseConnection();
}
return dataTable;
}
At View (Search View)
@model ClientLibrary.Entities.Person
<h1>Searching Person (Sql Injection)</h1>
<div style="margin-left:auto; margin-right: auto; ">
<div class="col-md-12" style="border:solid;border-width:1px;padding-top:20px; padding-bottom:20px;">
@using (Html.BeginForm("SearchByNameVulnerableReader", "Person", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.ValidationSummary()
<p>
@Html.LabelFor(m => m.Name, "Enter the person name:")
@Html.TextBoxFor(m => m.Name)
</p>
<p>
<input type="submit" class="btn btn-default" value="Search (IS-SQL-Reader)" />
</p>
<p>Vulnerable Search using SqlCommand-ExecuteReader</p>
}
</div>
</div>
Following screenshot corresponds to Search view in action
After we click search button we get something like
Now, let´s see what happens when we update the text field to something like: Manuel’ OR ”=’
If this is the case we see that application rescue all the values at person table:
So, we have been able to inject instructions for changing default application behaviour thought untrusted data, in fact we can see it debugging in chrome:
In this case an easy (and effective) solution for mitigating the problem is to use in-line parametrization in the following way:
public DataTable SearchByName(string name, string surname)
{
string query = string.Format("select * from [Persons] where Name like @name ");
SqlParameter[] sqlParameters = new SqlParameter[1];
sqlParameters[0] = new SqlParameter("@name", SqlDbType.VarChar);
sqlParameters[0].Value = Convert.ToString(name);
return ExecuteSelectQuery(query, sqlParameters);
}
public DataTable ExecuteSelectQueryReader(String query, SqlParameter[] sqlParameter)
{
SqlCommand myCommand = new SqlCommand();
DataTable dataTable = new DataTable();
DataSet ds = new DataSet();
try
{
myCommand.Connection = OpenConnection();
myCommand.CommandText = query;
if (sqlParameter != null && sqlParameter.Length > 0)
{
myCommand.Parameters.AddRange(sqlParameter);
}
using (SqlDataReader dataReader = myCommand.ExecuteReader())
{
dataTable.Load(dataReader);
}
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeSelectQuery - Query: " + query + " \nException: " + e.StackTrace.ToString());
throw;
}
finally
{
CloseConnection();
}
return dataTable;
}
Solutions and Native Defences: ORM, Patterns, etc…
The mere use of some ORM like Entity framework and secure coding patterns helps a lot to (partially) mitigate the risk of injection in web applications. From a general perspective and in similar way to other owasp vulnerability risk, is advisable is to apply what is referred as Security In Depth, which means that we will try to mitigate risks by implementing different security layers, this way, if one layer is compromised there are still others for protecting the application.
In order to be more specific, recommended actions for mitigating SQL Injection vulnerability are the following ones:
Least Minimum Privilege :
When applying to web application database, means that having different sql roles for managing the access to database resources gives us a much better security environment.
In-Line Parametrization:
This is the standard solution for mitigating most of SQL Injection vulnerabilities; this means to manipulate input params as strongly typed SqlParamenters instead of direct string concatenation. The most common sources of SQL Injections are those in which untrusted data is directly concatenated to query.
Vulnerable Code:
public DataTable GetDataTable(string name)
{
string query = "select * from [Persons] where Name like '" + name + "'";
return conn.ExecuteSelectQuery(query,null);
}
Parametrized Code
public DataTable GetDataTable(string name)
{
string query = string.Format("select * from [Persons] where Name like @name ");
SqlParameter[] sqlParameters = new SqlParameter[1];
sqlParameters[0] = new SqlParameter("@name", SqlDbType.VarChar);
sqlParameters[0].Value = Convert.ToString(name);
return conn.ExecuteSelectQuery(query, sqlParameters);
}
For a given ExecuteSelectQuery method. Something like
public DataTable ExecuteSelectQuery(String query, SqlParameter[] sqlParameter)
{
SqlCommand myCommand = new SqlCommand();
DataTable dataTable = new DataTable();
DataSet ds = new DataSet();
try
{
myCommand.Connection = OpenConnection();
myCommand.CommandText = query;
if (sqlParameter != null && sqlParameter.Length > 0)
{
myCommand.Parameters.AddRange(sqlParameter);
}
myAdapter.SelectCommand = myCommand;
myAdapter.Fill(ds);
dataTable = ds.Tables[0];
}
catch (SqlException e)
{
Console.Write("Error - Connection.executeSelectQuery - Query: " + query + " \nException: " + e.StackTrace.ToString());
throw;
}
finally
{
CloseConnection();
}
return dataTable;
}
Stored Procedures Parametrization
Another robust way of mitigating the risk of Sql Injection is to use “Stored Procedures Parametrization”, this approach complements previous one and it means that we must define the specific database types we are going to use as input parameters for stored procedures.
ORMs
In many cases ORMs like Entity Framework and NHibernate implement In-line parametrization by default which initially gives use a good level of protection. At this point it worth to mention that, despite of EF helps a lot for mitigating SQL Injection, is still possible having SQL Injection attacks even when using Entity Framework (surprise!). Unappropriated use of constructions using DbSqlQuery, DbRawSqlQuery, ObjectQuery or even EntityCommand method can harbour SQL Injections, which is more common than expected; similar to following code was found at ASP.NET MVC controller
public ActionResult GetBookDetails(string title)
{
bool showList = true;
if (string.IsNullOrWhiteSpace(title))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
DbSqlQuery<Book> books;
books = db.Books.SqlQuery("Select * from dbo.Books where title ='" + title + "'");
if (books == null)
return HttpNotFound();
return View("DetailList", books.ToList());
}
Use of Whitelisting for validating inputs
From a general perpective all untrusted data should always be validated against a whitelist of known allowed values. A whitelist it´s a very explicit control and it strictly filters out anything us don´t trust. Use a whitelist is preferable to the use of blacklist which implicitly establish what is not allow (all out of blacklist will be considered as safe!).
Note: These are, in a general sense, the kind of solutions adopted by WAFs (Web Application Firewalls). List of white and black lists for controlling traffic to your web
From a developer view, common approaches of whitelisting are:
- Use a regular expression (email address, etc..)
- Type conversion (integer, date, Guid, etc…)
- List of know good values (countries, products, etc…)
Following is an extreme sample
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
Dictionary<string, string> allowedUrls = LoadAllowedURLs();
if (!allowedUrls.ContainsKey(Request.Path))
{
Response.Redirect("Some_default_redirect_page.aspx");
}
}
}
Use a SAST
These are source code analysis tools which inspect all your code in searching of vulnerable patterns. Some authors’ recommends attaching them as post compile event for making web applications deployable code safer. The SASTs have their strengths and (of course) their weakness.
A very valid solution is NETSPI. It is really efficient in finding flaws and implementation bugs. On the free side we can find Security Code Scan which can be installed as Visual Studio plugin or Nuget package. It´s a pure open source Roslyn project that integrates seamly into your compilation pipeline making it a good friend for your continuous integration ecosystem.
Best Solution: IAST + RASP
Always from the point of view of defence in depth concept, using a modern IAST (Interactive Application Security Testing) can be considered as the best solution for fully detecting SQL Injection vulnerabilities. I´m not going to enter into details, but the techniques used in modern IASTs are more robust than SASTs ones. This does not means that SASTs are not a valid tool for detecting injections, they are, but IASTs are far more reliable, and of course, with almost zero false positives (SASTs on the other hand, has about 55% of false positives). In later articles I´ll try to do a comparison between both of them.
Following screenshot corresponds to the use of Hdiv .NET IAST Agent (tool in whose development I had the privilege of participating) over WebGoat ASP.NET Web Forms application (which is can be used for testing OWASP Risk):
As can be seen from screen, IAST is able to give information about the specific point in your code where the vulnerability is located. In some cases (if pdb file is deployed), IAST give you the specific line of code.
Once IAST has reported all vulnerabilities, developers will be in a position for code revisiting in order to apply previously commented techniques. Even more, if you want a really first class protection, you can also implement some integral solution like RASP. RASP stands for Runtime Application Self-Protection, and basically (I´ll describe these tools in detail in later articles also), this tool integrates into web development process (in a really light way) and helps applications to protect themselves during runtime.
I´ll try to provide more examples of injection attacks (different from SQL Injection one), like Command Injection, XPath Injection, LDAP Injection, etc.. in later articles inside this OWASP series