CXX.SQL.INJECT
对 SQL 命令中所用特殊元素进行可能的不当中性化处理导致的 SQL 注入
此检查器会标记 SQL 注入漏洞。如果 SQL 语句是使用未经验证的输入创建的,则恶意用户便可以通过攻击应用程序的特权而在此应用程序使用的数据库上注入字符串和执行任意 SQL 语句。
漏洞与风险
当代码输入未经妥善验证时,攻击者可以精心地将输入编制为允许攻击者执行任意 SQL 语句的格式。通过此类机会,攻击者可以:
- 读取应用程序数据库中的机密数据
- 修改应用程序数据库中的数据
- 执行任意命令,如删除数据库中的所有信息
缓解与预防
要避免此问题,最好是
- 创建 SQL 语句时仅使用常数字符串
- 使用安全库创建将使用输入数据的参数化 SQL 语句
- 在使用 SQL 语句中的输入前,添加验证代码
漏洞代码示例 1
以下是使用 SQLite C/C++ API 包含 SQL 注入漏洞的示例。
void cwe_89_example_2(sqlite3 *database)
{
const char *userName;
const char *itemName;
char *sql;
gets(userName);
gets(itemName);
strcat(sql, "SELECT * FROM items WHERE owner = '");
strcat(sql, userName);
strcat(sql, "' AND itemname = '");
strcat(sql, itemName);
strcat(sql, "'");
char *errMsg = 0;
sqlite3_exec(database, sql, NULL, 0, &errMsg);
}
Klocwork 针对第 14 行生成了 SQL 注入报告,指出已将不安全的 SQL 查询字符串用作命令。在本例中,为 `itemName` 使用输入 `“name'; DELETE FROM items; --”` 可能会发生 SQL 注入。这是因为未正确转义 SQL 查询中的保留字符,例如 `itemName` 字符串中的单引号字符。在本例中,假定用户名为 `'wiley'`,SQL 字符串被解析并执行为以下 3 个语句:
SELECT * FROM items WHERE owner = 'wiley' AND itemname = 'name'; DELETE FROM items; --'
执行 `DELETE` 语句将从数据库中的 `'items'`表中删除所有条目。
修正代码示例 1
使用未经验证的用户输入来构造 SQL 查询并且不转义保留字符是危险的做法。因此,您应使用 SQLite C/C++ API 中的其他函数来构造 SQL 查询中的预编译语句对象,并且将用户输入绑定到 SQL 查询的参数,在此过程中会适当处理保留字符。例如,您可以通过使用 `sqlite3_prepare_v2` 来构造预编译语句对象,并通过使用 `sqlite3_bind*` 函数来绑定用户输入中的查询的参数。此方法将正确转义 SQL 查询的保留字符(例如单引号字符),从而防止构造和执行 `"DELETE from items"` 命令。可以通过以下方式完成此操作:
void cwe_89_example_2_fixed(sqlite3 *database)
{
const char *userName;
const char *itemName;
const char *sql = "SELECT * FROM items WHERE owner = (?)AND itemname = (?)";
gets(userName);
gets(itemName);
sqlite3_stmt *statement;
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK) {
printf("sqlite3_prepare_v2 failure: %s", sqlite3_errmsg(database));
return;
}
if (sqlite3_bind_text(statement, 1, userName, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
printf("sqlite_3_bind_text unable to bind argument 1: %s", sqlite3_errmsg(database));
sqlite3_finalize(statement);
return;
}
if (sqlite3_bind_text(statement, 2, itemName, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
printf("sqlite_3_bind_text unable to bind argument 2: %s", sqlite3_errmsg(database));
sqlite3_finalize(statement);
return;
}
if (sqlite3_step(statement) != SQLITE_DONE) {
printf("sqlite_3_step failure: %s", sqlite3_errmsg(database));
}
sqlite3_finalize(statement);
}
扩展
此检查器可通过 Klocwork 知识库进行扩展。有关详情,请参阅调整 C/C++ 分析。