[英]How to prevent SQL injection when the statement has a dynamic table name?
我有这样的代码。
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
fullTableName
计算类似于:
public String getFullTableName(final String table) {
if (this.schemaDB != null) {
return this.schemaDB + "." + table;
}
return table;
}
这里schemaDB
是环境的名称(可以随时间更改), table
是表名(将被修复)。
schemaDB
值来自一个XML
文件,这使得查询容易受到 SQL 注入的攻击。
查询:我不确定如何将表名用作准备好的语句(如本示例中使用的name
),这是针对 SQL 注入的 100% 安全措施。
任何人都可以建议我,处理这个问题的可能方法是什么?
注意:我们将来可以迁移到 DB2,因此该解决方案应该与 Oracle 和 DB2 兼容(如果可能的话,与数据库无关)。
不幸的是,JDBC 不允许您将表名设为语句内的绑定变量。 (这样做是有原因的)。
所以你不能写,或实现这种功能:
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
并将TUSER
绑定到语句的表名。
因此,您唯一安全的方法是验证用户输入。 但是,最安全的方法不是验证它并允许用户输入通过数据库,因为从安全角度来看,您总是可以指望用户比您的验证更聪明。 永远不要相信一个动态的、用户生成的字符串,在你的语句中连接。
那么什么是安全的验证模式?
1)在代码中一劳永逸地创建所有有效的语句。
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
如果需要,可以使用select * from ALL_TABLES;
使这个创建本身动态select * from ALL_TABLES;
陈述。 ALL_TABLES
将返回您的 SQL 用户有权访问的所有表,您还可以从中获取表名和模式名。
2) 选择地图里面的语句
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
查看unsafeUserContent
变量如何永远不会到达数据库。
3) 制定某种策略或单元测试,以检查所有statementByTableName
是否对您的架构有效,以便将来对其进行演变,并且没有丢失任何表。
您可以 1) 验证用户输入确实是一个表名,使用无注入查询(我在这里输入伪 sql 代码,您必须对其进行调整以使其工作,因为我没有实际检查的 Oracle 实例有用) :
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
并在此处将您的 fullName 绑定为准备好的语句变量。 如果你有结果,那么它就是一个有效的表名。 然后您可以使用此结果来构建安全查询。
它有点像 1 和 2 之间的混合。您创建一个名为“TABLES_ALLOWED_FOR_DELETION”的表,然后用所有适合删除的表静态填充它。
然后你让你的验证步骤是
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
如果有结果,则执行 safe_table_name。 为了更加安全,标准应用程序用户不应写入该表。
我不知何故觉得第一个模式更好。
您可以通过使用正则表达式检查表名来避免攻击:
if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
}
使用如此受限制的字符集来注入 SQL 是不可能的。
此外,我们可以转义表名中的任何引号,并将其安全地添加到我们的查询中:
fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
StringEscapeUtils 带有 Apache 的 commons-lang 库。
我认为最好的方法是创建一组可能的表名并在创建查询之前检查该组中是否存在。
Set<String> validTables=.... // prepare this set yourself
if(validTables.contains(fullTableName))
{
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
//and so on
}else{
// ooooh you nasty haker!
}
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;
N
10
create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;
TNAME
MYTAB
create or replace procedure deltab(v in varchar2)
is
LvSQL varchar2(32767);
LvChk number;
begin
LvChk := 0;
begin
select count(1)
into LvChk
from TABS2DEL
where tname = v;
if LvChk = 0 then
raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
end if;
exception when others
then raise;
end;
LvSQL := 'delete from '||v||' where n = 10';
execute immediate LvSQL;
commit;
end deltab;
begin
deltab('MYTAB');
end;
select * from mytab;
没有找到行
begin
deltab('InvalidTableName');
end;
ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.