我正在尝试为我的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_literal
which is preferred到pg_escape_string
,但我的PHP版本为5.3.13,此命令仅适用于5.4.4及以上版本。
问题是:在这种情况下,这是防止注射攻击的最佳方法吗?这真的可以防止攻击吗?
另一个问题:如果用户想在他/她的密码中使用撇号,是否可以没有此错误?
答案 0 :(得分:3)
正如您所发现的那样,预编译语句不能用于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'");
但是,您可能会质疑让用户选择他们想要的任何密码是否是个好主意,而不进行过滤。
在它引起的问题中,立即引发的问题是你的连接电话:
$link = pg_connect("dbname=mydb host=localhost user=$user password=$old connect_timeout=1");
这里没有引用$old
中的密码,如果它包含空格字符,那么这将失败。这就是你提到这个问题的可能原因:如果我尝试设置像''这样的密码,我之后就无法改变它。
要处理pg_connect
电话中的特殊字符,文档会说:
每个参数设置的格式为keyword = value。周围的空间 等号是可选的。写入空值或值 包含空格,用单引号括起来,例如keyword ='a 值'。必须转义值中的单引号和反斜杠 用反斜杠,即\'和\。
因此,如果你开始接受任何东西,你应该提供一个能够逃脱的功能。
就个人而言,我会首先禁止这些字符出现在用户提供的密码中,理由是这是一个不必要的问题来源。由于编码问题,还有非US-ASCII字符。
答案 1 :(得分:3)
作为一个说明,我建议使用存储过程。如果需要,准备好的语句总是可以调用存储过程。
这里有一些非常简单的示例代码,大部分是从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权限并访问您想要在这里管理的任何表。还要注意警告。如果不适当地转义参数,则可以进行In-db SQL注入。请注意,此功能可以创建用户并更改密码。这里省略了关于检测用户是否被设置并且如果没有处理该情况的LSMB特定部分。