Using Stored Procedures
One design technique
that can prevent or mitigate the impact of SQL injection is to design
the application to exclusively use stored procedures for accessing the
database. Stored procedures are programs stored within the database, and
you can write them in a number of different languages and variants
depending on the database, such as SQL (PL/SQL for Oracle, Transact-SQL
for SQL Server, SQL:2003 standard for MySQL), Java (Oracle), or others.
Stored procedures can
be very useful for mitigating the seriousness of a potential SQL
injection vulnerability, as it is possible to configure access controls
at the database level when using stored procedures on most databases.
This is important, because it means that if an exploitable SQL injection
issue is found, the attacker should not be able to access sensitive
information within the database if the permissions are correctly
configured.
This happens because
dynamic SQL, due to its dynamic nature, requires more permissions on the
database than the application strictly needs. As dynamic SQL is
assembled at the application, or elsewhere in the database, and is then
sent to the database for execution, all data within the database that
needs to be readable, writable, or updateable by the application needs
to be accessible to the database user account that is used to access the
database. Therefore, when an SQL injection issue occurs, the attacker
can potentially access all of the information within the database that
is accessible to the application, as the attacker will have the database
permissions of the application.
With the use of stored
procedures, you can change this situation. In this case, you would
create stored procedures to perform all of the database access the
application needs. The database user that the application uses to access
the database is given permissions to execute the stored procedures that
the application needs, but does not have any other data permissions
within the database (i.e., the user account does not have SELECT, INSERT, or UPDATE rights to any of the application's data, but does have EXECUTE
rights on the stored procedures). The stored procedures then access the
data with differing permissions—for example, the permissions of the
user who created the procedure rather than the user invoking the
procedure—and can interact with the application data as necessary. This
can help you to mitigate the impact of an SQL injection issue, as the
attacker will be limited to calling the stored procedures, therefore
limiting the data the attacker can access or modify, and in many cases
preventing the attacker from accessing sensitive information in the
database.
SQL Injection in Stored Procedures
It
is often assumed that SQL injection can happen only at the application
level—for example, in a Web application. This is incorrect, as SQL
injection can occur at any level where dynamic SQL is used, including at
the database level. If unsanitized user input is submitted to the
database—for example, as a parameter to a stored procedure—and then it
is used in dynamic SQL, SQL injection can occur at the database level as
easily as at any other level.
Therefore, you should
be careful when handling untrusted input at the database level, and you
should avoid dynamic SQL wherever possible. In situations where stored
procedures are in use, the use of dynamic SQL can often indicate that
additional procedures should be defined at the database level to
encapsulate missing logic, therefore enabling you to avoid the use of
dynamic SQL within the database at all.
|
Using Abstraction Layers
When
designing an enterprise application it is a common practice to define
various layers for presentation, business logic, and data access,
allowing the implementation of each layer to be abstracted from the
overall design. Depending on the technology in use, this may involve an
additional data access abstraction layer such as Hibernate, or the use
of a database access framework such as ADO.NET, JDBC, or PDO. These
layers of abstraction can be a very useful place for the security-aware
designer to enforce safe data access practices that will then be used
throughout the rest of the architecture.
A good example of this
would be a data access layer that ensures that all database calls are
performed through the use of parameterized statements. Providing that the application did not access
the database in any way other than the data access layer, and that the
application did not then use the supplied information in dynamic SQL at
the database level itself, SQL injection is unlikely to be present. Even
more powerful would be to combine this method of accessing the database
with the use of stored procedures, as this would mitigate the risk even
further. This may also have the effect of easing implementation, as in
that case all of the methods of accessing the database will have been
defined, and would therefore be easier to implement in a well-designed
data access layer.
Handling Sensitive Data
A
final technique for mitigating the seriousness of SQL injection is to
consider the storage and access of sensitive information within the
database. One of the goals of an attacker is to gain access to the data
that is held within the database—often because that data will have some
form of monetary value. Examples of the types of information an attacker
may be interested in obtaining may include usernames and passwords,
personal information, or financial information such as credit card
details. Because of this, it is worth considering additional controls
over sensitive information. Some example controls or design decisions to
consider might be the following:
Passwords
Where possible, you should not store users' passwords within the
database. A more secure alternative is to store a salted one-way hash
(using a secure hash algorithm such as SHA256) of each user's password
instead of the password itself. The salt, which is an additional small
piece of random data, should then ideally be stored separately from the
password hash. In this case, instead of comparing a user's password to
the one in the database during the login process, you would compare the
salted hash calculated from the details supplied by the user to the
value stored in the database. Note that this will prevent the
application from being able to e-mail the user his password when he
forgets it; in this case, it would be necessary to generate a new,
secure password for the user and provide that to him instead.
Credit card and other financial information
You should store details such as credit cards encrypted with an
approved (i.e., FIPS-certified) encryption algorithm. This is a
requirement of the Payment Card Industry Data Security Standards
(PCI-DSS) for credit card information. However, you should also consider
encrypting other financial information that may be in the application,
such as bank account details.
Archiving
Where an application is not required to maintain a full history of all
of the sensitive information that is submitted to it (e.g., personally
identifiable information), you should consider archiving or removing the
unneeded information after a reasonable period of time. Where the
application does not require this information after initial processing,
you should archive or remove unneeded information immediately. In this
case, removing information where the exposure would be a major privacy
breach may reduce the impact of any future security breach by reducing
the amount of customer information to which an attacker can gain access.
Notes from an Incident Response
One
of the more interesting incident response engagements that I was
involved with was with a fairly large regional bank in the northeast
region of the United States. The client (a bank) had noticed that
something odd was going on when their server administrator saw that the
logs for one day were several times larger than they normally expected.
As such, they looked into it, and fairly quickly determined that they
were the victims of an SQL injection exploit.
In this case, the exploit
vector was fairly innocuous—it was an identifier that the application
used to determine which press release the user wanted to read in the
“News” section of the Web site. Unfortunately for the client, the press
release detail was not the only information stored in that database.
Also stored in that database were the mortgage application details of
every customer of the bank who had applied for a mortgage through the
Web site, including full names, Social Security numbers, phone numbers,
address history, job history, and so forth—in other words, everything
needed for identity theft, for almost 10,000 customers.
The bank in question
ended up writing to every one of its customers to apologize, and also
provided all of the affected customers with complimentary identity theft
protection. But had the bank paid some attention to where its sensitive
information was stored before the exploit happened the exploit probably
would not have been nearly as serious as it was.
|
Avoiding Obvious Object Names
For security reasons, you
should be careful with your choice of names for critical objects such
as encryption functions, password columns, and credit card columns.
Most application developers will use obvious column names, such as password, or a translated version such as kennwort
(in German). On the other side, most attackers are aware of this
approach and will search for interesting columns names (such as password) in the appropriate views of the database. Here's an example on Oracle:
SELECT owner||'.'||column_name FROM all_tab_columns WHERE upper(column_name)
LIKE '%PASSW%')
The information from
the table containing passwords or other sensitive information will be
selected in the next step of the attack. To see some examples of the
types of naming to avoid, refer to Table 1, which lists common variations and translations for the word password.
Table 1. Password in Different Languages
Word for Password | Language |
---|
password, pwd, passw | English |
passwort, kennwort | German |
Motdepasse, mdp | French |
wachtwoord | Dutch |
senha | Portuguese |
haslo | Polish |
To
make the attack more difficult, it could be a good idea to use an
unobvious table and column name for saving password information.
Although this technique will not stop an attacker from finding and
accessing the data, it will ensure that the attacker will not be able to
identify this information immediately.
Setting Up Database Honeypots
To become alerted if
someone tries to read the passwords from the database, you could set up
an additional honeypot table with a password
column that contains fake data. If this fake data were selected, the
administrator of the application would receive an e-mail. In Oracle, you
could implement such a solution by using a virtual private database
(VPD), as in the following example:
-- create the honeypot table
Create table app_user.tblusers (id number, name varchar2(30), password
varchar2(30));
-- create the policy function sending an e-mail to the administrator
-- this function must be created in a different schema, e.g., secuser
create or replace secuser.function get_cust_id
(
p_schema in varchar2,
p_table in varchar2
)
return varchar2
as
v_connection UTL_SMTP.CONNECTION;
begin
v_connection := UTL_SMTP.OPEN_CONNECTION('mailhost.victim.com',25);
UTL_SMTP.HELO(v_connection,'mailhost.victim.com');
UTL_SMTP.MAIL(v_connection,'[email protected]');
UTL_SMTP.RCPT(v_connection,'[email protected]');
UTL_SMTP.DATA(v_connection,'WARNING! SELECT PERFORMED ON HONEYPOT');
UTL_SMTP.QUIT(v_connection);
return '1=1'; -- always show the entire table
end;
/
-- assign the policy function to the honeypot table TBLUSERS
exec dbms_rls.add_policy (
'APP_USER',
'TBLUSERS',
'GET_CUST_ID',
'SECUSER',
'',
'SELECT,INSERT,UPDATE,DELETE');