[英]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.