如何在 Postgresql 中参数化 ALTER ROLE 语句?

Tha*_* MG 4 php postgresql escaping postgresql-9.2

我正在尝试为我的 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);
    }
?>
Run Code Online (Sandbox Code Playgroud)

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

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

pg_prepare($link,"change_user","ALTER ROLE $1 WITH ENCRYPTED PASSWORD '$2';");
Run Code Online (Sandbox Code Playgroud)

但即使我尝试PREPARE在 pgAdminIII 中执行此命令,我也会收到语法错误。事实上,Postgres 手册表明PREPARE只能准备“Any SELECT、INSERT、UPDATE、DELETE 或 VALUES 语句”。

然后我尝试使用该pg_escape_string()功能:

$user = pg_escape_string($_POST["user"]);
...
Run Code Online (Sandbox Code Playgroud)

当我使用普通密码时,这很有效,如果我尝试设置类似 的密码''",之后就无法更改它。我已经尝试过根据手册首选哪个,但我的PHP版本是5.3.13,并且此命令仅适用于5.4.4以上pg_escape_literal pg_escape_string

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

另一个问题:如果用户想在他/她的密码中使用撇号,是否可以不出现此错误?

Dan*_*ité 6

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

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

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

php-5.4.4 提供了pg_escape_identifier标识符,但旧版本没有提供任何引用标识符。pg_escape_string正如其描述和用户评论中所述,不适合这样做。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'");
Run Code Online (Sandbox Code Playgroud)

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

在它引起的问题中,立即出现的一个问题是在 connect 调用中:

$link = pg_connect("dbname=mydb host=localhost user=$user password=$old connect_timeout=1");
Run Code Online (Sandbox Code Playgroud)

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

为了处理pg_connect通话中的特殊字符,文档是这样说的:

每个参数设置的形式为关键字=值。等号周围的空格是可选的。要写入空值或包含空格的值,请用单引号将其引起来,例如,keyword = 'a value'。值中的单引号和反斜杠必须用反斜杠转义,即\' 和\。

因此,如果您决定接受任何内容,则应该提供一个执行此转义操作的函数。

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