Running a Web server seems simple to many people, with security updates provided by server software vendors supposedly sufficient. However, tying up security issues on the server software does not protect a site from common attacks based on Web applications. These attacks exploit common Web application coding mistakes, and can lead to complete site vulnerability.
1. SQL injection
2. Cross-site scripting
3. Remote code execution
4. Format string attacks
5. Username enumeration
The first two attack types are widely-enough used that it's worth looking at them in more detail below. Of the others, a short summary explains what they are and how they work.
Remote code execution allows an attacked to run code on the server and directly retrieve data there. In most cases, these attacks are taking advantage of poor Web application coding, typically from the register_globals setting in PHP of from XML-RPC applications. Fortunately, these attack types are fairly rare due to changes in behavior of recent PHP versions.
Format string attacks exploit unfiltered user input fields. The attacker adds format tokens to the input that prints data in memory, provides a denial-of-service assault, or some similar action. Format string attacks are very easily handled by verifying user input properly.
Finally, username enumeration where a Web server script to verify a username and password is attacked. By feeding a series of usernames and passwords, and analyzing the error messages generated for invalid combinations, valid usernames and passwords can be detected. The number of these attacks has decreased lately due to many applications now using the same message for invalid username-password combinations. Protecting against these attacks is also easier with strong password enforcement.
SQL injection exploits a database layer security hole in many applications. This is typically accomplished through a Web page user input field that accepts user entries and then uses that entry as a parameter in a SQL statement to query a database. This is a relatively common process, especially for sites that ask the user to enter a username and password, then query the SQL database to verify the user credentials. It needs to be pointed out that SQL injections differ depending on the database. SQL Server seems to be a regular target for injection attacks, and mySQL has its share. Variations in the handling of statements means some tailoring of the SQL injection attack differs a little depending on the database used.
The SQL injection vulnerability occurs when a SQL statement or user input (perhaps through a script or Web interface) is not handled properly, allowing embedded escape characters to be executed literally, essentially executing a command on the system. By expanding the embedded characters and handling them as a command, or by handling input using standard logic, unexpected behavior can be easily triggered if not properly managed by the application.
An example shows how simple this type of attack can be. We'll keep with SQL statements for this example, but injection attacks can actually be used where any user input is required. Suppose you have a SQL statement like this:
SELECT mylist FROM mytable WHERE name = $input;
where $input is input gathered from the user through a prompt or built up through several prompts to create a complex condition. Typically, the user would type something like "fred" in which case the statement would execute as:
SELECT mylist FROM mytable WHERE name = 'fred';
and return all matches to the name field. However, if the user types an input with a trailing extra single quote mark, such as "fred'", the behavior of the SQL parser changes. Typical behavior is that the single unmatched quotation mark causes a syntax error, since the command now reads:
SELECT mylist FROM mytable WHERE name = 'fred'';
How the syntax error is actually manifested depends on how the application is coded and its internal error-handling routines. Ideally, the application should return a message about an unknown user, use of illegal characters, or in some other way warn the user that their entry is invalidly formed. However, many applications do not do this properly, and the command is actually going to be interpreted literally by the SQL parser. This leaves the application open to abuse.
If the user notices this vulnerability and types the string:
fred' or 1=1--
at the input, the SQL statement parsed becomes:
SELECT mylist FROM mytable WHERE name = 'fred' or 1=1--';
The command has gone from a single component WHERE to a Boolean-based two-component clause. The second part is always true, since the two strings match, and the use of the OR ensure the entire clause is always true regardless of the first condition. (The two double dashes at the end of the user input is used to tell SQL to ignore the rest of the query, so a hanging single quote at the end of the statement does not cause a syntax error!)
This type of injection attack can yield information about the application and its databases. For example, if the user input was:
x' AND 1=(SELECT COUNT(*) FROM tablename); '
and the user made continual guesses at the tablename, the command ends up essentially executing
SELECT COUNT(*) FROM tablename;
which will display the number of records in "tablename". If the name is incorrect, the command fails, but it's easy to feed a script that changes "tablename" until something is returned. Then, the user has the name of the table and can do more damage. For example, if the SQL statement requiring input is this:
SELECT * FROM mytable WHERE name=$input;
and the user injects the string
fred; DROP TABLE mytable; --
then the SQL statement becomes:
SELECT * FROM mytable WHERE name=fred; DROP TABLE mytable; --
The actual string for the WHERE clause doesn't matter; the following statement is what causes the problems, deleting the table using its discovered name.
To return to the common issue of a username and password that is verified against a SQL database, the most common approach is to use a statement like this:
SELECT * FROM mytable WHERE username = $username AND password = $password;
where username and password are fields in mytable, and the two variables are the user input. If the user input field is not strongly typed, a user could enter this for the username:
which renders the statement to be
SELECT * FROM mytable WHERE username = 'admin'--' AND password = '';
Because the two dashes cause the rest of the command to be ignored, including the mismatched quotes and the lack of a password, the command essentially logs the user in as "admin" without a password required.
These SQL injection attacks are successful if the application does not properly handle errors, or does not use strongly types input fields. The solution for SQL injection attacks is very simple: do not allow user input to be directly embedded in SQL statement, and ensure that all user input is escaped and properly handled. Parameterized statements are the easy approach. Parameterized statements use variables or placeholders to pass user input to a SQL statement, feeding a static SQL statement.
Cross-site scripting is a similar technique to SQL injection, except it doesn't need a SQL database to act against. As with SQL injection, cross-site scripting is typically used against Web applications and are one of the most common forms of phishing attacks.
Despite the same origin approach, as well as improvements in client-side scripting language protection, cross-site scripting has become a major issue with malicious scripts served up from one site gaining access to content from another site, even across terminated browser sessions.
One form of cross-site scripting is non-persistent or reflected vulnerability. Reflected cross-site scripting is common and popular because it is very easy to use. With reflected cross-site scripting, a page of HTML with user data embedded is reflected to a user. Essentially, reflected cross-site scripting takes data from a Web client, stores it, and eventually send it back to a user in unfiltered HTML.
Phishing attacks are the usual target of reflected cross-site scripting. The most famous example was an attack on Google.com in late 2005 that allowed the attacker to mount phishing attacks and impersonate Google administrators.
Persistent cross-site scripting is dangerous because the script attack needs to be submitted to the Web server site only once, but continues to exist and execute (and collect data) continually. Even if protection against these attacks is added later, the damage is already done.
if this is used in a Web URL such as "http://www.mysite.com/welcome.html", the URL request in the browser would be something like this:
and the HTML would return the message :
This is a common approach for many sites, based on either cookie information or a user input string. However, if the target URL get this request instead:
Protecting against cross-site scripting attacks is not quite as simple as protecting against SQL injection attacks. Typically, the best protection is to ensure that all incoming data is of an approved format. A similar set of rules should control any data sent from the server. In both cases, using "known good" approaches should be used instead of rejecting "known bad" formats, to ensure better input and output controls.
The five Web application vulnerabilities explained above are the most threatening, but there are other vulnerabilities that have to be detected, managed, and defended against. Some of these other vulnerabilities tend to go in cycles, rising in popularity until Web developers are made aware of the issues and begin coding to avoid those problems by nature. A good example of this is the buffer overflow error which overloaded an application's buffer, crashed the application, and allow the session to be taken over by a remote user. Buffer overflows were widely used a few years ago, but is seen rarely lately.
The biggest issue for most Web developers and site managers is that the number of security issues that arise is far higher than the site developer's and administrator's abilities to handle them. While vulnerabilities are routinely detected and information about work-arounds and protection widely distributed, it's time-consuming and often difficult to keep up with the tide. By far the easiest approach to handling these issues is to develop Web application code that avoids most of these exploits naturally by using solid coding with proper validation or input and output, as well as sufficient specific error handling, right from the start.