Caution
安全提示: 默认字符集
The character set must be set either at the server level, or with the API function mysql_set_charset() for it to affect mysql_real_escape_string(). See the concepts section on character sets for more information.
参数 ¶
unescaped_string
The string that is to be escaped.
link_identifier
MySQL 连接。如不指定连接标识,则使用由 mysql_connect() 最近打开的连接。如果没有找到该连接,会尝试不带参数调用 mysql_connect() 来创建。如没有找到连接或无法建立连接,则会生成 E_WARNING 级别的错误。
https://www.jb51.net/w3school/php/func_mysql_real_escape_string.htm
The Attack
那么,让我们以显示攻击开始…
mysql_query(‘SET NAMES gbk’);
$var = mysql_real_escape_string(“\xbf\x27 OR 1=1 /*”);
mysql_query(“SELECT * FROM test WHERE name = ‘$var’ LIMIT 1”);
在某些情况下,这将返回超过1行。我们来分析一下这里发生了什么:
因此,对mysql_real_escape_string()的调用会插入反斜杠,并且在我们的“转义”内容中有一个免费的`字符。实际上,如果我们要查看 gbk 字符集中的 $ var `,我们会看到:
ç¸-'OR 1 = 1 / *
这是[正是攻击所需要的。
查询
这部分只是一个形式,但是这里是渲染的查询:
SELECT FROM test WHERE name =’ç¸-‘OR 1 = 1 / ‘LIMIT 1
恭喜你,你用mysql_real_escape_string()成功地攻击了一个程序…
The Bad
情况变得更糟PDO默认使用MySQL模拟_准备好的语句。这意味着在客户端,基本上通过mysql_real_escape_string()(在C库中)执行sprintf,这意味着以下操作将导致注入成功:
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
现在,值得注意的是,您可以通过禁用模拟的准备语句来防止这种情况:
mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
因为服务器期望utf8 …
mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");
因为我们已经正确设置了字符集,所以客户端和服务器匹配。
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
因为我们已经关闭了模拟的准备好的语句。
$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));
因为我们已经正确设置了字符集。
$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();
因为MySQLi一直都在准备好声明。