如本文所述,SQL注入攻击或SQLi是通过在其输入字段中插入恶意的SQL语句来执行来利用SQL语句的潜在漏洞的一种方法。它于1998年首次亮相,从那时起,它主要针对零售商和银行帐户。与其他形式的攻击(例如DDOS攻击,跨站点脚本(XSS)或DNS劫持)一起使用时,它可能导致大规模的结果。
术语:
- 验证:验证是检查输入是否满足一组条件的过程(例如,字符串包含独立的单引号)。
- 清理:清理是修改输入以确保输入有效的过程(例如将单引号加倍)。
为了避免SQL注入,必须正确过滤和清除要在动态SQL中串联的所有输入。
SQL攻击的剖析:
SQL攻击分为以下两个部分:
- 研究:查看与数据库连接的用户端应用程序的易受攻击的部分。
- 攻击:输入恶意字段,这些恶意字段可以使查询变型,从而发挥自己的优势。
范例1:
考虑以下用Java编写的身份验证表单的代码:
String query = "SELECT userName, balance FROM accounts"
+ "WHERE userID=" + request.getParameter("userID") +
"and password='" + request.getParameter("Password") + "'";
try
{
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(query);
while (rs.next())
{
page.addTableRow(rs.getString("userName"),
rs.getFloat("balance"));
}
}
catch (SQLException e)
{}
在正常情况下,用户输入其用户名和密码,这将生成以下语句以供执行:
SELECT userName, balance
FROM accounts
WHERE userID=512 and password='thisisyoda'
可能的SQL注入攻击将利用password字段生成一个布尔表达式,该布尔表达式将使该表达式在所有情况下均评估为true。想象将userID和password字段设置为
userID = 1' or '1' = '1
password = 1' or '1' = '1
然后,SQL语句变为
SELECT userName, balance
FROM accounts
WHERE userID='1' OR '1'='1' and
password='1' OR '1'='1'
查询将返回一个值,因为条件(OR 1 = 1)始终为true。这样,系统就在不知道用户名和密码的情况下对用户进行了身份验证。
可以使用以下准备的语句来创建参数化查询来缓解此漏洞:
String query = "SELECT userName, balance "+
"FROM accounts WHERE userID = ?
and password = ?";
try {
PreparedStatement statement = connection.prepareStatement(query);
statement.setInt(1, request.getParameter("userID"));
ResultSet rs = statement.executeQuery();
while (rs.next())
{
page.addTableRow(rs.getString("userName"),
rs.getFloat("balance"));
}
} catch (SQLException e)
{ ... }
如果攻击者试图向userID字段提供一个非简单整数的值,则statement.setInt()将抛出SQLException错误,而不是允许查询完成。
范例2 :
考虑身份验证期间的另一种攻击类型:
String query = "SELECT userID, userName, passwordHash"+
" FROM users WHERE userName = '"
+ request.getParameter("user") + "'";
int userID = -1;
HashMap userGroups = new HashMap();
try
{
Satement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(query);
rs.first();
userID = rs.getInt("userID");
if (!hashOf(request.getParameter("password")).equals(rs.getString("passwordHash")))
{
throw BadLoginException();
}
String userGroupQuery = "SELECT group FROM groupMembership"+
" WHERE userID = " + userID;
rs = statement.executeQuery(userGroupQuery);
while (rs.next())
{
userGroup.put(rs.getString("group"), true);
}
}
catch (SQLException e){}
catch (BadLoginException e){}
常规查询如下。
SELECT userID, userName, passwordHash
FROM users
WHERE userName = 'Anannya'
攻击者可能会将以下内容注入到userName字段中。
Anannya';
INSERT INTO groupMmbership (userID, group)
VALUES (SELECT userID FROM users
WHERE userName='Anannya', 'Administrator'); --
因此,实际查询将更改为:
SELECT userID, userName, passwordHash FROM
users WHERE userName = 'Anannya';
INSERT INTO groupMmbership (userID, group)
VALUES (SELECT userID FROM users
WHERE userName='Anannya', 'Administrator'); --'
这将导致另一个SQL语句被附加到实际语句中,从而导致用户被添加到Administrator数据库中。可以通过将准备好的语句与参数化查询一起使用来缓解这种攻击,如下所示。
String query = "SELECT userID, userName, passwordHash"+
" FROM users WHERE userName = ?";
try
{
PreparedSatement statement =
connection.prepareStatement(userLoginQuery);
statement.setString(1, request.getParameter("user"));
ResultSet rs = statement.executeQuery();
}
例子3 :
考虑下面讨论的查询漏洞的另一个示例:
String query = "INSERT INTO users VALUES(" +
request.getParameter("userName") + ");";
一般查询将是:
INSERT INTO users VALUES("Anannya")
考虑攻击者是否在userName字段中输入以下查询:
"Anannya); DROP TABLE users;"
查询将更改为:
INSERT INTO users VALUES("Anannya"); DROP TABLE users;
该查询在执行后会完全删除用户表。同样,这里的解决方法是准备好的语句。
在Java中使用预处理语句有什么帮助?
准备好的语句“清除”了输入。这意味着它确保将用户输入的任何内容都视为SQL中的字符串字面量,而不是SQL查询的一部分。它还可能会转义某些字符并检测/删除恶意代码。在其他语言(例如PHP,可以使用filter_input或filter_input_array清理字符串。