【问题标题】:How to build a query with an optional filter resistant to SQL injections?如何使用可抵抗 SQL 注入的可选过滤器构建查询?
【发布时间】:2022-01-17 14:48:56
【问题描述】:

有这样一个数据库:

还有这样一个存储函数:

CREATE FUNCTION fetch_mentor(direction_type_list_in text, education_type_list_in text, name_in text, city_id_in integer) RETURNS json
    LANGUAGE plpgsql
AS
$$
DECLARE
    direction_type_list_inner INT[];
    education_type_list_inner INT[];
BEGIN
    direction_type_list_inner = STRING_TO_ARRAY(direction_type_list_in, ',');
    education_type_list_inner = STRING_TO_ARRAY(education_type_list_in, ',');

    RETURN (SELECT JSON_AGG(rows)
                FROM (SELECT m.id                                     AS "ID"
                           , FORMAT('%s %s', m.firstname, m.lastname) AS "fullName"
                           , m.photo_url                              AS "photoURL"
                           , m.video_url                              AS "videoURL"
                           , (SELECT JSON_AGG(mc.name)
                                  FROM mentors.mentor_competence mc
                                  WHERE mc.mentor_id = m.id)          AS "competenceList"
                           , (SELECT JSON_AGG(el.name)
                                  FROM mentors.mentor_employment            me
                                           INNER JOIN lists.employment_list el ON el.id = me.employment_id
                                  WHERE me.mentor_id = m.id)          AS "competenceList"
                           , m.certified                              AS "certified"
                           , cl.display_name                          AS "cityName"
                           , (SELECT JSON_AGG(dtl.display_name)
                                  FROM mentors.mentor_direction_type            mdt
                                           INNER JOIN lists.direction_type_list dtl ON mdt.direction_type_id = dtl.id
                                  WHERE mdt.mentor_id = m.id)            "directionList"
                           , (SELECT JSON_AGG(etl.display_name)
                                  FROM mentors.mentor_education_type            met
                                           INNER JOIN lists.education_type_list etl ON met.education_type_id = etl.id
                                  WHERE met.mentor_id = m.id)            "educationList"
                          FROM mentors.mentor                m
                                   LEFT JOIN lists.city_list cl ON m.city_id = cl.id
                          WHERE approved = TRUE
                            AND (direction_type_list_in ISNULL OR 
                                 m.id IN (SELECT m.id
                                              FROM mentors.mentor_direction_type mdt
                                              WHERE mdt.direction_type_id = ANY (direction_type_list_inner)))
                            AND (education_type_list_inner ISNULL OR
                                 m.id IN (SELECT m.id
                                              FROM mentors.mentor_education_type met
                                              WHERE met.education_type_id = ANY (education_type_list_inner)))
                            AND (name_in ISNULL OR
                                 (m.firstname LIKE FORMAT('%%%s%%', $3) OR m.lastname LIKE FORMAT('%%%s%%', $3)))
                            AND (city_id_in ISNULL OR m.city_id = city_id_in)
                     ) rows);
END;
$$;

ALTER FUNCTION fetch_mentor(TEXT, TEXT, TEXT, INTEGER) OWNER TO postgres;

返回这样一个json:

[
    {
        "ID": 3,
        "fullName": "fsafd 413",
        "photoURL": "sadf",
        "videoURL": "dsa",
        "competenceList": [
            "a",
            "s",
            "f"
        ],
        "employmentList": [
            "a",
            "b",
            "c"
        ],
        "certified": false,
        "cityName": null,
        "directionList": [
            "x",
            "z"
        ],
        "educationList": [
            "offline"
        ]
    }
]

以下参数进入输入:

type FetchMentorsParams struct {
    DirectionTypeList []int   `json:"directionTypeList"`
    EducationTypeList []int   `json:"educationTypeList"`
    MentorName        *string `json:"mentorName"`
}

所有过滤器都是可选的。也就是说,如果没有过滤器,则输出所有记录。例如,如果Direction Type List = [1,2],则只应返回表mentor_direction_type 中的mentor_id 位于direction_type_id = 1 or 2 所在列的那些记录。等等。 但是有没有一种方法可以在没有存储函数的情况下形成一个查询来抵抗 SQL 注入?如果你执行len(direction Type List) != 0 之类的操作,然后在 for 循环中添加子查询,那么查询将容易受到 sql 注入的攻击。

一般来说,尝试在一个查询中完成所有事情有多好?我看到一个选项:在主查询之前,查询mentor_direction_typementor_education_type,然后查询where mentor_id in (the result of querys)

而且进行诸如-(SELECT JSON_AGG(el.name) FROM mentors.mentor_employment me INNER JOIN lists.employment_list el ON el.id = me.employment_id WHERE me.mentor_id = m.id) AS "competenceList"之类的查询是正常的,我不确定它是否经过优化,但我没有看到任何其他选项。

我正在使用 Go、SQLX、PGX。

【问题讨论】:

    标签: go query-builder sqlx pgx


    【解决方案1】:

    分解你的问题。

    函数

    还有这样一个存储函数:

    尽管编写plpgsql 函数似乎来自其他编程语言是合乎逻辑的,但在 Postgresql 中,这些函数有一个警告,即规划器将无法正确评估函数内部运行的成本。这意味着随着数据的增长,您的计划员将变得越来越偏离,认为您的功能得分有点低,而实际上它可能会很高。 除非您的函数可以使用 volatility modifiers 之一,否则我将始终将其编写为标准查询语句。

    如果优化是一个问题,您可以使用embed go 包并使用漂亮整洁的 .sql 文件:-)

    过滤器和 SQL 注入

    所有过滤器都是可选的。

    我也很欣赏这样一个事实,即您想要一个能够接受任何参数集并输出正确输出的查询“全部统治”。 然而,这可能会变成评估所有可能组合和场景的兔子洞。

    有时最好有一点冗余,同时有更小、更好的意图定义的查询。

    并且进行这样的查询是正常的 - (SELECT JSON_AGG(el.name) FROM Mentors.mentor_employment me INNER JOIN lists.employment_list el ON el.id = me.employment_id WHERE me.mentor_id = m.id) AS “competenceList”,我不确定它是否经过优化,但我没有看到任何其他选项。

    嵌套查询不一定是坏事,请记住,对我们来说清楚的 SQL 对规划者来说并不总是清楚的。 在判断子查询之前,我建议您使用 EXPLAIN ANALYZE 运行 fetch_mentor 函数的内容,这将告诉您规划器将如何解决它,并帮助您了解是否存在任何瓶颈。

    如果您是EXPLAIN ANALYZE 的新手,您可以将其输出复制并粘贴到https://flame-explain.com/visualize/input 等工具中

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-04
      • 1970-01-01
      • 2020-07-07
      相关资源
      最近更新 更多