问题:golang服务中如何将下游服务SQL注入的风险降到最低?

我正在用 golang 编写一个半面向外部的 Web 服务,让用户可以查询有关其帐户的信息,这些信息分布在多个内部遗留服务中。

我的服务将用户输入字符串传递到多个后端 RESTful API,这些 API 根据字符串执行 MySQL 查找以生成结果,这些结果将传递回我的服务以提供给用户。

从历史上看,这些遗留的后端服务并没有暴露给用户输入,所以我不确定它们是否有适当的防护措施来防止 SQL 注入。

通常,我会使用Prepared Statements来防止 SQL 注入,以防止数据库引擎将用户字符串视为可解析的,但在这种情况下,我不控制数据库调用——它们在下游很远,这不切实际立即审核它们。

我可以在我的 golang 代码中做些什么来尽可能多地清理用户输入,以最大程度地减少 SQL 注入漏掉的风险?这最终是一种权宜之计,直到可以审计所有下游数据库调用的注入安全性。

解答

在过去的工作中,我为我们的服务维护了一个允许临时查询的设施。它允许管理人员请求报告,而无需等待数周的代码部署(回到部署需要数周时间的古怪日子)。

我们不通过使服务接受任意字符串作为输入并将它们作为 SQL 执行来支持临时报告查询。这是非常不安全的,我相信你知道。

它的工作方式是将报告查询以及所需的查询参数数量存储在数据库中。

CREATE TABLE ManagerQueries (
  id INT PRIMARY KEY,
  query TEXT NOT NULL,
  description TEXT NOT NULL,
  num_params TINYINT UNSIGNED NOT NULL DEFAULT 0
);

INSERT INTO ManagerQueries
SET query = 'SELECT COUNT(*) FROM logins WHERE user_id = {0} AND created_at > {1}',
    description = 'Count a given user logins since a date',
    num_params = 2;

管理器前端可以通过其主键请求查询,而不是通过在 Web 请求中指定任意 SQL 字符串。

只有 DBA 以及可能知道如何编写安全查询的其他开发人员或管理人员才被允许向此存储库添加新查询,因此可以保证查询已经过测试和审查。

prepare()execute()

在您的情况下,您的代码可能无法直接访问旧服务的数据库,因此您无法进行准备/执行和使用绑定参数。您必须提交包含集成值的静态查询。

在其他语言中,您可以通过转义使任何字符串值安全地插入 SQL 查询。请参阅 MySQL C API 函数mysql_real_escape_string()。

数值更容易。您无需转义任何内容,只需确保数值是真正的数字即可。一旦将动态值转换为数字,就可以安全地插回任何 SQL 字符串。

不幸的是,我认为 golang SQL 包不支持任何转义功能。这已被要求作为一项功能,但据我所知,尚无支持的实现。请参阅此处的讨论:https://github.com/golang/go/issues/18478

因此,您可能必须实现自己的转义功能。例如,您可以在官方 MySQL C 实现之后对其进行建模:https://github.com/mysql/mysql-server/blob/8.0/mysys/charset.cc#L716

请注意,这比仅使用正则表达式替换有点棘手,因为您需要考虑多字节字符集。