Java 查询字符串中需要动态 table 变量时的 SQL 注入问题
SQLinjection concerns when dynamic table variable is needed in Java query string
场景:
我有大约 50 tables 我希望能够在每次用户单击 Java "update all" 按钮时从本地数据库中提取数据并刷新。
我无法写入远程数据库,或者我只能使用准备好的语句或存储过程创建视图。
为每个 table 创建一个自定义准备语句似乎很难维护,但我也不想让我的代码容易受到 SQLInjection 的攻击。
为了降低风险,我有一个 table 名称的白名单(来自本地数据库),有限的字符串长度,指定的 tables(数据和日期),并添加了标记 table_name 到字符串连接中。
我希望通过添加标记字段,大多数 SQL 注入都会因语法错误而失败。这些将通过 try catch 处理,伤口不会造成伤害(可能是错误的)。
虽然可以更改 table 名称,但我不太担心,因为它必须是具有适当结构的合法 table,而且我正在获取所有table 无论如何。
问题:
使用下面的预准备语句我还应该注意哪些其他 SQL 注入问题?
List<String> tableNamesWhiteList;
...
for (String t:tableNamesWhiteList)
{
if(t.length < 30)
{
try{
String stm = "select '"+t+"' as table_name, data, date from " + t +" where data = ?";
pst = con.prepareStatement(stm);
pst.setInt(1, 100);
rs = pst.executeQuery();
....
希望这是有道理的
我将通过指出我不是安全分析师来开始我要说的内容。
我不认为你在这里所做的是有效的或必要的。首先,SQL 注入依赖于解析器读取到第一个有效的 SQL 语句的末尾并在继续之前执行它。因此,如果上面的代码完全 是易受攻击的,(我不认为是,但我稍后会介绍,)您的查询
select '"+t+"' as table_name, data, date from " + t +" where data = ?
可以通过设置 t
等于
来攻击
1' from pg_users; drop table pg_users;
并且解析器将执行 SELECT
,然后执行 DROP
,然后在 AS
上阻塞,但到那时为时已晚。这就是 SQL 注入的工作原理。
不过,您使用的是准备好的语句这一事实意味着,在执行任何查询字符串之前,必须从一端到另一端解析整个查询字符串,并且任何通过 [=13 注入的尝试=] 几乎肯定会在查询的末尾留下无效的语法,这会在解析器执行之前阻塞解析器。即使情况并非如此,您的 table 名称也来自您自己的代码,从预设列表中挑选,如果它被泄露,您需要担心的问题比 SQL 注入更大。
编写安全代码固然好,但为代码镀上一层装甲会使它变得缓慢且难以维护,而且很少有正当理由。
不过,您可能会考虑一个替代方案,它用于我几年前开发的商业产品。另一种方法是将 50 个 table 的查询存储在另一个 table 中,可能由 table 名称索引。代码可能看起来像这样:
String stm = "SELECT query FROM table_queries WHERE table_id = ?";
pst = con.prepareStatement(stm);
pst.setInt(1, tableID);
rs = pst.executeQuery();
stm = rs.first().getString("query");
pst = con.prepareStatement(stm);
pst.setInt(1, 100);
rs = pst.executeQuery();
场景:
我有大约 50 tables 我希望能够在每次用户单击 Java "update all" 按钮时从本地数据库中提取数据并刷新。 我无法写入远程数据库,或者我只能使用准备好的语句或存储过程创建视图。
为每个 table 创建一个自定义准备语句似乎很难维护,但我也不想让我的代码容易受到 SQLInjection 的攻击。
为了降低风险,我有一个 table 名称的白名单(来自本地数据库),有限的字符串长度,指定的 tables(数据和日期),并添加了标记 table_name 到字符串连接中。
我希望通过添加标记字段,大多数 SQL 注入都会因语法错误而失败。这些将通过 try catch 处理,伤口不会造成伤害(可能是错误的)。
虽然可以更改 table 名称,但我不太担心,因为它必须是具有适当结构的合法 table,而且我正在获取所有table 无论如何。
问题:
使用下面的预准备语句我还应该注意哪些其他 SQL 注入问题?
List<String> tableNamesWhiteList;
...
for (String t:tableNamesWhiteList)
{
if(t.length < 30)
{
try{
String stm = "select '"+t+"' as table_name, data, date from " + t +" where data = ?";
pst = con.prepareStatement(stm);
pst.setInt(1, 100);
rs = pst.executeQuery();
....
希望这是有道理的
我将通过指出我不是安全分析师来开始我要说的内容。
我不认为你在这里所做的是有效的或必要的。首先,SQL 注入依赖于解析器读取到第一个有效的 SQL 语句的末尾并在继续之前执行它。因此,如果上面的代码完全 是易受攻击的,(我不认为是,但我稍后会介绍,)您的查询
select '"+t+"' as table_name, data, date from " + t +" where data = ?
可以通过设置 t
等于
1' from pg_users; drop table pg_users;
并且解析器将执行 SELECT
,然后执行 DROP
,然后在 AS
上阻塞,但到那时为时已晚。这就是 SQL 注入的工作原理。
不过,您使用的是准备好的语句这一事实意味着,在执行任何查询字符串之前,必须从一端到另一端解析整个查询字符串,并且任何通过 [=13 注入的尝试=] 几乎肯定会在查询的末尾留下无效的语法,这会在解析器执行之前阻塞解析器。即使情况并非如此,您的 table 名称也来自您自己的代码,从预设列表中挑选,如果它被泄露,您需要担心的问题比 SQL 注入更大。
编写安全代码固然好,但为代码镀上一层装甲会使它变得缓慢且难以维护,而且很少有正当理由。
不过,您可能会考虑一个替代方案,它用于我几年前开发的商业产品。另一种方法是将 50 个 table 的查询存储在另一个 table 中,可能由 table 名称索引。代码可能看起来像这样:
String stm = "SELECT query FROM table_queries WHERE table_id = ?";
pst = con.prepareStatement(stm);
pst.setInt(1, tableID);
rs = pst.executeQuery();
stm = rs.first().getString("query");
pst = con.prepareStatement(stm);
pst.setInt(1, 100);
rs = pst.executeQuery();