【问题标题】:How to parameterize an ALTER ROLE statement in Postgresql?如何在 Postgresql 中参数化 ALTER ROLE 语句?
【发布时间】:2013-09-20 14:10:26
【问题描述】:

我正在尝试为我的 PSQL 数据库创建一个 PHP 接口,我希望一些在 PSQL 上注册的本地用户能够登录到我的数据库。我会首先为每个用户创建每个用户名,使用通用密码,如“Password123”,然后用户可以稍后更改他/她的密码。

为此,我想到了使用一个简单的 PHP 表单:

<form action="" method="post">
    <table>
    <tr> <td> User: </td> <td> <input type="text" name="user" /> </td> </tr>
    <tr> <td> Old password: </td> <td> <input type="password" name="old" /> </td> </tr>
    <tr> <td> New password: </td> <td> <input type="password" name="new1" /> </td> </tr>
    <tr> <td> Repeat new password: </td> <td> <input type="password" name="new2" /> </td> </tr>
     <tr> <td> <input type="submit" name="submit" value="Change password" /> </td> </tr>
</form>

<?php
    if ($_POST) {
        $user = $_POST["user"];
        $old = $_POST["old"];
        $new1 = $_POST["new1"];
        $new2 = $_POST["new2"];

        $link = pg_connect("dbname=mydb host=localhost user=$user password=$old connect_timeout=1");
        if (!$link) { 
            die("Error connecting to mydb: ".pg_last_error($link)); 
        }

        if ($new1 <> $new2) {
            pg_close($link);
            die("New passwords do not match.");
        }

        $res = @pg_query($link,"ALTER ROLE $user WITH ENCRYPTED PASSWORD '$new1';");
        if ($res) {
            echo "Password successfully changed!<br>";
        } else {
            echo "Failed to change password...<br>";
        }

        pg_close($link);
    }
?>

它确实像我预期的那样工作!

但是我读到有一些 SQL 注入攻击可以在这样的表达式上进行,其中 SQL 查询中有一个简单的变量插值。所以我读到PREPARE 语句是进行此类查询的最安全方法。我希望做类似的事情:

pg_prepare($link,"change_user","ALTER ROLE $1 WITH ENCRYPTED PASSWORD '$2';");

但我得到一个语法错误,即使我尝试在 pgAdminIII 中 PREPARE 这个命令。实际上,Postgres manual 表明 PREPARE 只能准备“任何 SELECT、INSERT、UPDATE、DELETE 或 VALUES 语句”。

然后我尝试使用pg_escape_string()函数:

$user = pg_escape_string($_POST["user"]);
...

这在我使用普通密码时效果很好,如果我尝试设置像''" 这样的密码,之后我将无法更改它。我根据手册试过pg_escape_literalwhich is preferredpg_escape_string,但是我的PHP是5.3.13版本,这个命令只适用于5.4.4以后。

问题是:在这种情况下,这是防止注入攻击的最佳方法吗?这真的可以防止攻击吗?

另一个问题:如果用户想在他/她的密码中使用撇号,是否有可能没有这个错误?

【问题讨论】:

    标签: php postgresql escaping postgresql-9.2


    【解决方案1】:

    正如您所发现的,准备好的语句不能用于像ALTER USER 这样的“实用程序语句”,它们超出了这个问题的范围。

    必须正确引用用户名和密码,要正确引用有几个问题需要考虑。

    用户是一个标识符,密码是一个字符串文字,这就是为什么标识符不是用单引号括起来而密码是。它们不遵循相同的句法规则,不能用相同的函数引用。

    php-5.4.4 为标识符提供pg_escape_identifier,但旧版本不提供任何引用标识符。 pg_escape_string 不适合这种情况,如其描述和用户 cmets 中所述。 PostgreSQL 本身提供了quote_ident 函数,但是为了调用它,应该进行单独的查询。基本上这可能是这样的:

    $pgr=pg_query_params($dbconn, 'SELECT quote_ident($1)',
                         array(pg_escape_string($_POST["user"])));
    // error check on $pgr omitted for brievity
    list($quoted_user) = pg_fetch_array($pgr);
    $quoted_password = pg_escape_string($_POST["password"]);
    $res=pg_query($dbconn, 
                  "ALTER USER $quoted_user WITH ENCRYPTED PASSWORD '$quoted_password'");
    

    但是,您可能会质疑让用户选择他们想要的任何密码而不进行过滤是否是个好主意。

    在它引起的问题中,立即引发的问题是在您的连接调用中:

    $link = pg_connect("dbname=mydb host=localhost user=$user password=$old connect_timeout=1");
    

    这里$old 中的密码没有被引用,如果它包含空格字符,这将失败。这可能是您提到的这个问题的原因:如果我尝试设置像“'”这样的密码,之后我将无法更改它

    为了处理pg_connect 调用中的特殊字符,文档这样说:

    每个参数设置的形式为keyword = value。周围空间 等号是可选的。写入空值或值 包含空格,用单引号括起来,例如,keyword = 'a 价值'。值中的单引号和反斜杠必须转义 带有反斜杠,即 \' 和 \.

    因此,如果您打算接受任何内容,则应该提供一个进行此转义的函数。

    就我个人而言,我会首先在用户提供的密码中禁止这些字符,因为这是不必要的问题来源。由于编码问题,以及非 US-ASCII 字符。

    【讨论】:

    • 感谢您的回复!不幸的是,我的 PHP 是 5.3.13,所以我必须更新它以使用 pg_escape_identifier。我使用 WAMP,所以我会看看是否可以只更新 PHP 而不会破坏 WAMP。至于禁止密码中的某些字符,PHP中是否有禁止这些字符的内置函数?还是我必须使用一些正则表达式来构建这个函数,搜索不在允许列表中的其他字符?
    • @ThalesMG:我上面提供的那段代码不需要pg_escape_identifier。同样对于过滤字符,是的,使用preg_match 或类似的正则表达式就可以了。
    • 确实,您使用与 PSQL 的连接来引用 $user。但是后来连接已经成功了,对吧?所以我不能在连接之前使用它。而密码是用PHP函数pg_escape_string转义的,是不是和调用PSQL一样,返回SELECT quote_literal($password)的结果?不过,我会尝试对密码进行限制。
    • @ThalesMG:对于ALTER USER 语句,无论如何都需要建立连接。额外的 SELECT 只是从这个连接中受益。还有关于pg_escape_string:功能上它与SELECT quote_literal(...) 基本相同,只是在后一种情况下你需要引用quote_literal 的参数,所以会有鸡和蛋的问题。
    【解决方案2】:

    作为说明,我建议为此使用存储过程。如果需要,准备好的语句总是可以调用存储过程。

    这里有一些非常简单的示例代码,大部分是从 LedgerSMB 复制的(并稍作修改):

    CREATE OR REPLACE FUNCTION save_user(
        in_username text,
        in_password TEXT
    ) returns bool
    SET datestyle = 'ISO, YMD' -- needed due to legacy code regarding datestyles
    AS $$
        DECLARE
    
            stmt text;
            t_is_role bool;
        BEGIN
            -- WARNING TO PROGRAMMERS:  This function runs as the definer and runs
            -- utility statements via EXECUTE.
            -- PLEASE BE VERY CAREFUL ABOUT SQL-INJECTION INSIDE THIS FUNCTION.
    
           PERFORM rolname FROM pg_roles WHERE rolname = in_username;
           t_is_role := found;
    
           IF t_is_role is true and t_is_user is false and in_pls_import is false THEN
              RAISE EXCEPTION 'Duplicate user';
            END IF;
    
            if t_is_role and in_password is not null then
                    execute 'ALTER USER ' || quote_ident( in_username ) ||
                         ' WITH ENCRYPTED PASSWORD ' || quote_literal (in_password)
                         || $e$ valid until $e$ ||
                          quote_literal(now() + '1 day'::interval);
            elsif  t_is_role is false THEN
                -- create an actual user
                    execute 'CREATE USER ' || quote_ident( in_username ) ||
                         ' WITH ENCRYPTED PASSWORD ' || quote_literal (in_password)
                         || $e$ valid until $e$ || quote_literal(now() + '1 day'::interval);
           END IF;
    
           return true;
    
        END;
    $$ language 'plpgsql' SECURITY DEFINER;
    

    请注意,这是一个安全定义函数。通常建议您将此功能设置为由非数据库超级用户的用户拥有。这样的用户将需要 createrole 权限并访问您要在此处管理的任何表。还要注意这个警告。如果您没有适当地转义参数,则可以进行数据库内 SQL 注入。请注意,此功能既可以创建用户也可以更改密码。 LSMB 特有的部分,关于检测用户是否被设置,如果没有设置则处理。

    【讨论】:

    • 感谢您的回答!我看到您的代码中有一些未声明的变量,例如in_pls_importt_is_user,但我认为这是因为您从admin module in LedgerSMB 中提取了文本。制作一个执行查询的函数change_password,然后对查询SELECT change_password(new_password) 进行参数化的想法非常好,我没有这个想法!但是代码会警告 SQL 注入攻击...如果我在此函数中使用 PREPARE 语句,我会受到攻击吗?
    • 你不能准备一个实用程序语句,因为它没有查询计划(同样的原因你不能参数化它)。代码警告 SQL 注入,因为这些语句不能参数化。这些值进入函数参数化,因此需要确保它们被正确转义。保护措施是在标识符上使用quote_ident(),在文字值上使用quote_literal()。 (是的,未声明的变量是由于我删除了逻辑中特定于 LSMB 的部分而没有得到全部)。
    • 最后一个警告:由于函数可能是SECURITY DEFINER,如果您要更改当前用户的密码,请确保设置密码SESSION_USER而不是CURRENT_USER,因为后者将是在函数中本地设置为函数的所有者!
    • 我尝试使用您的想法,并创建了一个更改给定用户密码的函数,PREPAREd 使用SELECT change_pass('user','pass') 的语句,它成功了!那么它是安全地准备好的吗?关于SECURITY DEFINER,我编写脚本只更改登录数据库的用户的角色,所以我不使用这些变量(SESSIONCURRENT_USER)。
    • 问题是prepare只准备到“调用这个函数”的级别。该函数必须创建一个 SQL 字符串并执行它,并且在那里您有可能进行 SQL 注入。再次始终使用 quote_literalquote_ident
    猜你喜欢
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-06
    • 2013-06-17
    • 1970-01-01
    相关资源
    最近更新 更多