【问题标题】:sql injection - how to sanitize program generated sql clause?sql 注入 - 如何清理程序生成的 sql 子句?
【发布时间】:2011-11-09 15:15:21
【问题描述】:

在标准 Ajax 中,whereorder by SQL 子句由程序(而非用户)提供,例如

var url = ".select?dd=emp&where="+escape("emp_tp='abc' and hire_dt<current_date-'2 years' and super_emp_id is distinct from emp_id")

在服务器上回答

$where = (isset($_GET['where'])) ? pureClause($_GET['where']) : null;
$order = (isset($_GET['order'])) ? pureClause($_GET['order']) : null;
...
$query = $query.(($where)?" where $where":'').(($order)?" order by $order":'');

问题是pureClause 函数应该是什么样子?

现在pureClause 如果存在以下任何一种情况,就会引发错误:

; select insert update delete drop create truncate

如果其他注入导致查询失败,没关系,只要数据完好

对我来说这似乎足够了,但在我心里,我知道我错了。

说明:

  • 在 Postgres 中准备好的语句虽然非常快,但设置和维护起来很麻烦 - 它们适用于使用良好的查询,但不适用于自定义查询。
  • 为每个事务创建一个准备好的语句是一个 巨大的 db 命中。如果可以在应用级别获得安全性,则更可取。

最后,考虑 where 子句

emp_tp='abc' and hire_dt=current_dt-'2 years' and super_emp_id is distinct from emp_id

这里有多少占位符?这需要在被输入带有占位符的准备好的语句之前正确解析,对吗?还是我完全错过了这条船?


主要事实:

  • 为参数化的预处理语句编写 SQL 子句解析器并不实际
  • 编写保证无害的 SQL 子句清理程序并不实际

解决方案:

对于 SELECTS,随机 SQL 可能是个问题:既然保护数据库太难了,那就让数据库保护自己吧!有不同的用户有不同的角色/权限。使用只读用户进行选择。对于普通 SQL,这保证了这些语句不会产生 DML。

最佳实践:四个 db 用户访问

  1. developer,做所有事情(从不在网络应用中用作连接)
  2. dml - 几乎可以在所有东西上选择 / dml(必须用于 dml)
  3. read - 可以选择(用于所有选择,无论是准备好的还是文本)
  4. login - 只能执行登录/密码功能(在登录过程中使用)

密码保护:

  • dmlread 可能无法通过 select 或 dml 访问密码数据
  • login通过受保护的函数访问密码数据,例如,
函数登录(用户名,密码) - 返回 user_id 函数 set_password( usr_id, password ) - 设置密码
  • 只有login 可以运行login()set_password() 函数
  • 根据您的数据库,login可能需要 sql 访问密码列
  • 根据您的数据库,password 列可能会受到保护;如果没有,那么应该从user 表中移出到它自己的安全表中

使用管理员工具在mysql 中进行设置大约需要 30 分钟,其中包括编写登录功能和拆分密码列的时间。

【问题讨论】:

  • 使用占位符。动态生成的查询不会改变这一点。我发现这种方法比foo_real_escape_string 的(应该)过时的方法不太理想。如果我想要寻找“drop”怎么办?
  • @pst - 1) 占位符不暗示准备好的语句吗? 2) 如果 where 子句类似于 "emp_tp='abc' and hide_dt>current_date-'2 years' and super_emp_id!=emp_id" 将需要解析子句并为每个部分创建一个占位符并提取常量与列名等- 我错过了什么吗?
  • 啊。一个智能问题的美丽示例,其中“使用准备好的陈述”模因不是答案。可悲的是,它可能仍将最终获得最高投票的贡献。密切相关:Escaping field names in PDO statements
  • @Pekka 我不建议使用字段名称作为准备好的语句参数。您链接到的question 有一个非常特殊的要求,其中字段名称是用户输入的一部分。在这里,我只是将传入的字段名称参数映射到实际的表字段名称,并将传入的输入放入准备好的语句参数中。
  • 准备好的语句工作量太大,所以列名是一个附带问题。核心问题是,我们至少可以做些什么来确保一个有效的语句,不确定 where 和子句,不执行 dml?因此,我对 raise exception if 子句的想法有一个 dml 动词。

标签: javascript sql security sql-injection


【解决方案1】:

您所做的是对 sql 注入的定义,无法对其进行清理。您不能以安全的方式故事结束传递WHERE 子句。您必须在服务器端构建这部分查询。您没有意识到这一点的事实意味着您必须阅读更多关于 sql 注入的信息,明确询问 StackOverflow 是解决此问题的不安全方法。令人担心的是,您可能永远无法了解此漏洞的基本原理。

$order 可以通过白名单以安全的方式完成。例如:

if(in_array($_GET['order'],$list_of_rows)){
   $order=$_GET['order'];
}

如果您要传入表名或列名,请确保根据白名单检查它,否则这将是 sql 注入。

【讨论】:

  • 首先,已经阅读并将阅读更多关于 sql 注入的内容。其次,这里的sql注入只有在导致dml时才会受到伤害。换句话说, where 和 order 子句 可以被污染,只要它们不损害数据 - 例如,如果它出错,那很好。有了这些非常有限的目标,你还认为编写一个合适的过滤器是不可能的吗?
  • @cc young 危险看起来像这样:$order="(select if(substr(password,0,1),0,sleep(30)) from mysql.user where Name='root')"。子选择和联合选择可用于从另一个表,甚至另一个数据库中获取数据。白名单是必需的,讨厌的字符的黑名单将不起作用。
  • @cc young 同样在这个例子中,我使用了盲目的 sql 注入,如果攻击者正确地猜到了密码哈希中的一个字符,那么 HTTP 请求将需要 30 秒才能完成。这称为“带外”攻击。
  • 很好的例子!在我提交的答案(即只读或安全连接)中,还需要排除对users 表和/或特定列的访问。曾经在客户端/服务器环境中一直这样做,每个人都有自己的数据库连接并被分配不同的角色。现在我确信混合方法是正确的,而不是完全放弃这个想法并使用一个连接 - 不同的连接具有不同的安全风格,即使总是使用准备好的语句。
  • @cc young 我很担心,我认为这不能解决问题的根源。我仍然认为参数化查询是最好的解决方案。请记住,这是您见过的第一个 sql 注入漏洞,您的对手多年来一直在研究它们。 exploit-db.com
【解决方案2】:

知道了!通过仅被授予数据库 SELECT 权限的数据库用户(连接)路由所有这些查询!

尝试的 DML 会阻塞。这并不能防止 DoS 攻击(有很多方法可以做到这一点!),但确实可以保护数据。也不会进行安全查询,例如登录。但是对于客户端生成的 WHERE 和 ORDER,以防止 DML 为目标,这应该可以正常工作。

10/15 年前总是为不同的角色设置不同的用户,但应用层等已经摆脱了这种习惯。重新投资于这些原则可能是个好主意。

除非听到不同的声音会将其标记为正确答案 - 它满足所有标准,但它避开了编写消毒剂理论上不可能的挑战。

【讨论】:

  • 我在看到提前案例要求之前写了占位符评论。不幸的是,如果没有适当的 SQL 表达式解析器和数据库的语法重建,高级要求几乎不可能安全地进行。如果用户确实需要这种控制,则此解决方案 +1;我完全忘记了能够强制分离关注点。在逻辑上隔离连接并防止代码中的意外“串扰”可能需要一点纪律。
  • @pst - 谢谢。真的认为始终保持只读连接是个好主意。 dml 语句通常很容易参数化 - 在 dml 连接上使用它们,在只读上选择。任何人都没有解决的是准备好的语句的db cost。由于选择经常是临时的,只读连接既节省了解析工作,又节省了数据库的额外成本
【解决方案3】:

始终使用准备好的语句。它将处理输入转义并避免 sql 注入。不需要像pureClause 这样的黑客。 查看mysqli_stmt::prepare()

【讨论】:

  • 进一步评论,如果他试图通过 ajax 构建查询以发送过来,请将这些片段分成 json(即 {"where": {"logic": "and",比较: [{“key”:“woot”,“val”:“yelp”},{“key”:“woot2”,“val”:“yelp2”,“comparison”:“!=”}]}}。然后他可以运行他自己的自定义查询构建器并构建一个非常好的准备好的语句。
  • 同意,在 postgres 中我总是为常见任务做 - 提供速度和保护。但是准备好的语句维护起来很昂贵 - 如果应用程序层可以通过其他方式实现安全性,那么仅为了安全性为每个 sql 交互创建一个 custom 准备好的语句似乎是一个巨大的不必要的打击。不得不使用 mysql,在 php mysql 库中没有看到准备好的语句。将检查 mysqli_stmt,但是,也就是说,不能保证准备好的语句与其他接口,所以仍然想要一个像样的 pureClause 函数
  • PDO 也支持占位符。 +1 推荐mysql_real_escape_string或类似的。 (占位符甚至可以一起使用动态生成的 SQL 语句,所以真的没有任何借口。)
  • -1:在清理列名时,mysql_real_escape_string 和准备好的语句都是 utterly useless。这只是重申一个通常是正确建议的模因,除非它不是。
  • @Pekka 我不建议使用字段名称作为准备好的语句参数。您链接到的question 有一个非常特殊的要求,其中字段名称是用户输入的一部分。在这里,我只是将传入的字段名称参数映射到实际的表字段名称,并将传入的输入放入准备好的语句参数中。
【解决方案4】:

正如@Stephen 建议的那样,提供 WHERE 作为对象,然后解析对象并生成安全 SQL 变量在哪里 = { emp_tp:{ 条件:相等 值:'abc' } 变种顺序 = { emp_tp: 'ASC' }

以 json 格式发送:

var params = {
  w: where,
  o: order
}
$.post(url,params,function(result){...}, 'json');

在 PHP 中

$where = isset($_POST['w']) ? json_decode($_POST['w') : array();
if (!empty($where)) {
  foreach ($where as $field => $data) {
     // validate that field exists
     // validate that operator is valid
     $sql .= sprintf('%s %s "%s"', $field, $data->operator, mysql_escape_string($data->value));
  }
}

【讨论】:

  • 考虑“emp_tp='abc' and hide_dt=current_dt-'2 years' and super_emp_id!=emp_id” - 解析可能很重要。我对我来说是,如果查询失败(因此无需检查有效字段等),只要不造成任何伤害,那是完全可以的
  • @cc 我认为您需要检查有效的字段名称,因为据我所知,没有任何方法可以逃避它们。 +1 - 这至少是朝着正确的方向发展
  • @Pekka - 在这种情况下,如上所述,失败是可以的。唯一好的是数据被破坏。因此,无需检查列名。但可能是错的。
  • @cc young 所以卫生(防止 SQL 注入)不是问题?
  • @Darhazer 这是一个字段可能合法存在的潜在危险,例如用户表中的password,但通过构建恶意 where 子句,您可能会泄露属于另一个用户的密码哈希。跨度>
猜你喜欢
  • 2011-05-05
  • 2020-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-16
相关资源
最近更新 更多