Security is a multidimensional issue. Security risks can come from anyone. You could write bad error handling code or be too generous with permissions. You could forget what services are running on your server. You could accept all user inputs. And the list goes on. To give you a head start on protecting your machines, your network, and your code, here are 3 tips to follow a safer network strategy.
Even if you don’t read the rest of this article, remember one thing, “don’t trust user input.” If you always assume that data is well formed and good, then your troubles are about to begin. Most security vulnerability revolves around the attacker providing malformed data to the server machine.
Trusting that input is well formed can lead to buffer overruns, cross-site scripting attacks, SQL injection attacks and more. Let’s look at each of these potential attacks in more detail.
1. Protect Against Buffer Over-runs
A buffer overrun occurs when the data provided by attacker is bigger than what the application expects, and overflows into internal memory space. Buffer overruns are primarily a C/C++ issue. They are a menace, but generally easy to fix. The developer did not anticipate externally provided data that was larger than internal buffer. The overflow causes corruption of other data structures in memory, and the corruption can often lead to the attacker running malicious code. There are also buffer underflows and buffer overruns caused by array indexing mistakes, but they are less common. Take a look at the following C++ code snippet :
void DoSomething(char *cSrc1, DWORD cSrc2) {
char cDest[32];
memcpy(cDest, cSrc1,cSrc2);
}
What’s wrong with it? Actually, there’s nothing wrong with this code if cSrc1 and cSrc2 come from a trusted source, such as code that did not trust the data and so validate it to be well formed and of the correct size. However if the data comes from an untrusted source and has not been validated, then the attacker (the untrusted source) could easily make cSrc1 larger than cDest, and also set cSrc1 to be larger than cDest. When memcpy copies the data into cDest, the return address from DoSomething is clobbered because cDest is next to the return address on the function’s stack frame, and the attacker makes the code perform malicious operations.
The way to fix this is to distrust user input and not to believe any data field in cSrc1 and cSrc2:
This function should be modified in following ways. First, it should require the caller to provide the length of the buffer. Of course, you should not blindly trust this value! Next, in a debug build, the code will probe the buffer to check that it is indeed large enough to hold the source buffer, and if not, it will probably cause an access violation and throw the code into a debugger. It’s surprising how many bugs you can find when doing this. Last, and most important, the call to memcpy is defensive; it copies no more data than the destination buffer can hold.
2. Prevent Cross-site scripting
Cross-site scripting vulnerabilities are Web-specific issues and can compromise a client’s data through a flaw in a single Web page. Imagine the following ASP.NET code fragment:
Response.Write(“Hello,”+Request.QueryString(“name”));
How many of you have seen code like this? You may be surprised to learn it’s buggy! Normally, a user would access this code using a URL like :
http://aks.com/welcome.aspx?name=Abhishek
The c# code assumes that the data is always well formed and contains nothing more than a name. Attackers , however, abuse this code and provide script and HTML as the name. If you typed the following URL
http://aks.com/welcome.aspx?name=(script)alert(‘hi’);
You’d get a web page that displays a dialog box, saying “hi!”. You will say, “So what”. Imagine that the attacker convinces a user to click on a link like this, but the querystring contains some really nasty script and HTML to get your cookie and post it to a site that the attacker owns. The attacker now has your private cookie information or worse.
There are two ways to avoid this. The first is not to trust the input and be strict about what comprises a user’s name. For example, you could use regular expressions to check that the name contains only a common subset of characters and is not too big You cannot squeak a HTML or script through this regular expression. Don’t use a regular expression to look for invalid characters and reject the request if such characters are found because there is always a case that will slip by you.
The second defense is to HTML-encode all input when it is used as output. This will reduce dangerous HTML tags to more secure escape characters.
3. Don’t Require sa Permissions (Prevent SQL Injection)
The last kind of input trust attack I want to discuss is SQL injection. Many developers write
code that takes input and uses that input to build SQL queries to communicate with a back-end data store. Take a look at the following code snippet:
void DoQuery(string Id) {
SqlConnection sql = new SqlConnection (@”data source=localhost;” + “user id = sa; password = password;”);
sql.Open();
sqlstring = “SELECT hasshipped” + “ FROM shipping WHERE id = “‘+ Id + “’”;
SqlCommand cmd = new SqlCommand (sqlstring,sql);
}
This code is seriously flawed for three reasons. First, this connection is made from the Web Service to SQL Server as the system administrator account, sa. You will see why this is bad, shortly. Second, notice the clever use of “password” as the password for the sa account!
However, the real cause for concern is the string concatenation that builds the AQL statement. If a user enters an ID of 1001, then you get the following SQL statement, which is perfectly valid and well formed.
SELECT hasshipped FROM shipping WHERE id = ‘1001’
However, attackers are more creative than this. They would enter an ID of “‘1001’ DROP table shipping—“, which would execute the following query:
SELECT hasshipped FROM shipping WHERE id = ‘1001’ DROP table shipping – ’;
This changes the way query works. Not only does the code attempt to determine if something has shipped or not, it goes on to drop (delete) the shipping table! The – operator is a comment in SQL and it makes it easier for an attacker to build a valid, yet dangerous, series of SQL statements!
At this point you are probably wondering how any user could delete a table in the SQL Server database. Surely only admins can do a task like that. You are right. But here you are connecting to the database as sa, and sa can do anything it wants to do on a SQL Server database. You should never connect as sa from any application to SQL Server; rather, you should either use windows Integrated authentication, if applicable, or connect as a predefined account with appropriately restricted rights.
Fixing the SQL injection issue is easy. Using SQL stored procedures and parameters, the following code shows how to build a query like this – and how to use a regular expression to make sure that the input is valid because our business dictates that a shipping ID can only be numeric and between four and ten digits in length:
Regex r = new Regex(@”^\d{4,10}$”);
SqlConnection sql Conn = new SqlCommand (str, sqlConn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add (“@ID”,Id);
Buffer overruns, cross-site scripting, and SQL injection attacks are all examples of trusting input. All these attacks can be mitigated by believing that all input is evil, until proven otherwise.
Abhishek Kumar Sinha
(ps gave it for department magazine last year, unfortunately it was rejected)