Database Denial of Service: Attacks
Today’s post will discuss database denial of service attacks so later we can consider how to stop them.
From the security researcher’s perspective I cannot help but be impressed by the diversity of database DoS attacks. Many such attacks are pretty dumb – they seem to be written by a person who does not understand SQL, writing horrible queries that are the opposite of efficient. Some exploits are so simple – yet clever – that we are amazed the targeted vulnerability was not found in quality assurance tests. But dumb or not, these attacks are effective. For example you could start a couple different searches on a website, choose a very broad list of values, and hit ‘search’. The backend relational system starts to look at every record in every table, chewing up memory and waiting for slow disk reads.
Let’s look more closely at a couple different classes of denial of service attacks:
Abuse of Functions
The abuse of database functions is, by my count of reported vulnerabilities related to DoS, the single most common type of database DoS attack. There have been hundreds, and it seems like no externally accessible feature is safe.
This class of attack is a bit like competitive judo: as you shift your weight in one direction, your opponent pushes you in the same direction to make you lose your balance and fall over. A judo expert will use your weight against you, just like an attacker uses database features against you. For example, if you implement restrictions on failed logins attackers may try bad passwords until they lock all legitimate users out. If you implement services to automatically scale up to handle user requests attackers can scale the database up until it collapses under its own weight, or the bill becomes ruinous, or you hit a billing threshold and service is shut down. There is no single attack vector, but there is a whole range of ways to misuse database features.
This class of attacks is essentially an attacker getting a database function to misbehave. Typically it occurs when a database command confuses the database, the query parser, or a sub-function enough to lock up or crashes. Relational databases are complex gestalts of many interdependent processes, so the loss of a single service can often cause the entire database to grind to a halt.
One example is an attacker sending malformed Remote Procedure Calls, incomprehensible to the parser, which cause it to simply stop. Malformed XML and TDS calls have been used the same way, as well as SNMP queries. Pretty much every database communication protocol has, at one time or another, been fooled or subverted by requests that are formatted correctly but violate the expectations of the database developers in some way that causes a problem.
SQL injection is the best known type of functional abuse: SQL strings are bound into a variable passed to the database, so the database processes a very different query than was expected. SQLi is not typically associated with DoS – it is more often employed as a first step in a database takeover because most attackers want to control the database but don’t want to be detected, but it SQL injection works and is used in both ways.
Back to judo: every feature you have can be used against you.
Complex queries are an attack class that works by giving the database too much work to do. Attackers find the most resource-intensive process accessible, and kick off a few dozen requests.
- Computed columns and views: Computed columns are virtual columns, typically created from the results of a query, and usually stored in memory. A view is a virtual table, the contents of which are also derived from a query. If the query selects a large amount of data the results occupy a large space in memory. And if the column or view is based on a complex query, it requires significant processing power to create. Exposure of computed columns and views has been the source of database DoS attacks in the past, with attackers continually refreshing views to slow down the database.
- Nested queries & recursion: Recursion is when a program calls itself; each time it recreates declared parameters or variables in memory. A common attack is to place a recursive call within a cursor
FORloop; after a few thousand iterations the database runs out of cursors or memory and comes to a halt.
INoperator: This operator tests whether a supplied variable matches any value within a set. The operation itself is very slow – even if the number of values to be compared is small. An attacker can inject the
INoperator into a query to compare a large set of values against a variable that never matches. This is also called the snowflake search because it is like attempting to match two unique snowflakes, but the database continues to search regardless.
- Cartesian products and joins: The
JOINoperation combines rows from two or more tables. A cartesian product is the sum of all rows from all tables specified in the
FROMclause. Queries which calculate cartesian products on a few large tables generate huge result sets – possibly as large as the entire database. Any operations on a cartesian product can overwhelm a database.
- User defined functions: Similar to computed columns and views, any user-defined function gives an attacker
carte blancheto abuse the database with whatever query they choose. Attackers leverage any of the above complex queries.
Attackers attempt to exploit any complex query they can access. All these abuses are predicated on the attacker being able to inject SQL into some part of the database or abuse legitimate instances of complex operations. A couple complex queries running in parallel are enough to consume the majority of a database platform’s resources, badly slowing or completely stopping the database.
Bugs and Defects
Attackers exploit defects by targeting a specific weakness or bug in the database. To succeed the attacker needs to know or guess the type of database in use, and must know or learn of a weakness in code or design that can cause the database to slow down, exhaust its internal resources, or enter a deadlocked state. Database developers and testers check to make sure new features work, but they rarely try obscure test cases or fuzz inputs with random parameters. This is why historically so many bugs have led to database DoS; each time a vulnerability becomes public, attackers ‘weaponize’ it to create exploit code and then attack every web application they can find.
Buffer overflows have long been the main vulnerability. There have been a few dozen buffer overflow attacks on Oracle that can take down a database – sometimes even without user credentials when attacks leveraged the
PUBLIC privilege. SQL Server has its own history of similar issues, including the named pipes vulnerability. Attackers have taken down DB2 by sending UDP packets. In the last decade there has been a steady parade of bugs that enable attackers – often remotely and without credentials – to knock over databases. Attack specifics and targeted features vary but the same attacks are used against new features every year. Database vendors constantly add new features and modules; as well as new ways to connect, communicate, and cooperate; and old attacks keep finding new chinks in the armor.
If you thought the previous sections looked a bit like a list of database features, you are right. SQL injection uses a specific pathway into the database, but denial of service may target anything – including the way the application uses the database. Databases must expose some functions to application platforms to enable data management. But if the application does not successfully protect the exposed functions, the underlying database is exposed to anyone who uses the app. Phrased another way: if an application can be used to misuse a database, it will.
For example a common attack against retail websites is to to put a few thousand items in a shopping cart, continually adding new items and refreshing the cart. The database must validate each item is in stock, so it queries the inventory control database for each item in the cart. An attacker only needs a couple concurrent sessions to stall the site.
Attacks leverage legitimate application and database functions, focusing on those that require complex processes running behind the scenes. It does not take long for an attacker to identify good targets, because they tend to be the operations that are already slow to respond to user input. Developers and DBAs tend to point fingers each another and deny their own responsibility, but there is no single way to address this type of attack.
Some of this discussion of database DoS over the past decade may look like ancient history. But DoS attacks in general are on the rise; tactics have changed but the most important change has been moving up the stack. Database and application layer attacks have been somewhat born again, and the same old avenues and exploits are being leveraged all over again. As new hacking concepts and approaches evolve, attackers look at the features and functions they have hacked previously to see how they can apply their new toys. We saw buffer injection attacks for over a decade, because they continued to work as attackers found new areas of vulnerable database code. We continue to see SQLi attacks because they work. We see attacks against the network protocols because DBAs still forget to configure them correctly.
Our next post will connect the dots to present a number of different responses to database layer DoS.