即使在使用mysql_real_escape_string()函数时,是否有SQL注入的可能性?
考虑这个示例情况。SQL是用如下PHP构造的:
我听过很多人对我说,即使使用mysql_real_escape_string()函数,这样的代码仍然是危险的,并且可能被黑客攻击。但我想不出任何可能的利用?
像这样的经典注射剂:
不要工作。
您知道有没有可能通过上面的PHP代码进行的注入?
- 通常,最好在PHP代码中进行密码验证,以便显示更详细的错误(无效用户/无效密码)。
- @我知道,以上只是一个简单的例子来说明我的观点。
- 始终使用准备好的语句。安全性、语句重用的性能优势、标准化编码和库维护(在我看来)总是权衡其他任何"捷径"方法。
- @Thiefmaster-我不喜欢给出详细的错误,如无效用户/无效密码…它告诉蛮力商人他们有一个有效的用户ID,这只是他们需要猜测的密码。
- 但从可用性的角度来看,这是可怕的。有时,您不能使用您的主昵称/用户名/电子邮件地址,并在一段时间后忘记这一点,或网站删除了您的帐户不活动。然后,如果你继续尝试密码,甚至可能让你的IP被屏蔽,即使只是你的用户名无效,这是非常烦人的。
- 请不要在新代码中使用mysql_*函数。它们不再被维护,并且折旧过程已经开始。看到红盒子了吗?学习准备好的语句,并使用pdo或mysqli-本文将帮助您决定哪一个。如果您选择PDO,这里有一个很好的教程。
- "泰瑞?"Ko:他们不会从PHP中删除mysql_*函数,至少不会很快。也许在2050。想想看,如果他们删除了它,所有执行PHP自动更新的服务器都将使所有网站不起作用。那太荒谬了。
- @machineaddict,自5.5(最近发布)以来,mysql_*功能已经产生E_DEPRECATED警告。ext/mysql延长期已超过10年。你真的那么妄想吗?
- 由于大多数生产网站不打印错误,e_已弃用是无用的。在"所有"网站从MySQL功能切换之前,它不会被删除。即使在我工作的地方,我也必须使用MySQL扩展,因为即使他们认为它不会很快被删除。也许在php 6.0中。会看到…
- @机器指示它最终将被移除。服务器并不像您所说的那样真正执行自动更新。大多数服务器运行的是Linux的LTS版本,所以它们仍然运行相对较旧的PHP版本(许多服务器仍在php 5.1或5.2上)。如果他们在下一个主要版本的PHP中删除它,那么就有足够的时间停止使用MySQL_*函数(而且认真地说,多年来没有人使用它,它只是在遗留代码中),因为在新版本进入LTS版本之前,这需要时间(可能几年)。
- 只有一种最终的方法可以保护您不受SQL注入的影响。只需检查变量是否包含您期望的内容。如果需要整数,请使用ctype_digit…在大多数情况下,你应该用"或"来包围它。在变量匹配引号中转义…
- 最后两个破折号(--)后面的[空格]字符可能会使查询有效。aaa'或1=1--[此处为空格]
- @Loenix:有了Ints,可能还有更好的方法:与其检查,不如简单地将其转化为您所期望的。$value = (int) $value;或$value = intval($value);。它处理负面信号之类的事情,ctype_digit不会。
- @我不同意你的看法。最好的方法总是检查值,因为这并不意味着所有的整数都是预期的,我们不想插入一个用户不想要的值。如果要插入数据,则必须:检查所期望的内容,并将值格式化为标准化或可操作的。在这里,如果您检查它,并得到一个格式不正确的用户名,您可以返回"嘿,您的值无效,请修复它。"
- @Loenix:如果你是出于商业原因(例如:确保一个电话号码看起来像一个电话号码)进行过滤,那是一回事。但是出于技术原因的过滤是错误的。SQL注入不是由错误的数据引起的,而是由错误的代码引起的。如果他们真的想输入的话,一个人应该可以有一个名称alert("①'?@\'½¶?")。如果它会破坏你的应用,那么你的应用已经被破坏了。出于技术原因拒绝这样一个名字充其量是一种创可贴,充其量是一种虚假的安全措施。
- mysqli_real_escape_string()。
- @machineaddict他们刚刚删除了php 7.0上的扩展名,但现在还不是2050。
简短的回答是肯定的,是的,有一种方法可以绕过mysql_real_escape_string()。好的。对于非常模糊的边缘情况!!!!
答案不那么简单。这是基于这里演示的攻击。好的。进攻
那么,让我们从展示攻击开始……好的。
在某些情况下,这将返回一行以上。让我们分析一下这里发生了什么:好的。
选择字符集好的。
为了使这种攻击起作用,我们需要服务器在连接上期望的编码,既将'编码为ASCII格式,即0x27,也将最终字节为ASCII \,即0x5c。结果表明,MySQL5.6默认支持5种编码:big5、cp932、gb2312、gbk和sjis。我们在这里选择gbk。好的。
现在,注意这里使用SET NAMES非常重要。这将设置服务器上的字符集。如果我们使用对c-api函数mysql_set_charset()的调用,我们就可以了(2006年以来的mysql版本)。但更多关于为什么在一分钟内…好的。
有效载荷好的。
我们将用于此注入的有效负载从字节序列0xbf27开始。在gbk中,这是一个无效的多字节字符;在latin1中,它是字符串?'。注意,在latin1和gbk中,0x27本身就是一个文字'字符。好的。
我们选择这个有效载荷是因为,如果我们在它上面调用addslashes(),我们会在'字符之前插入一个ascii \,即0x5c。所以我们最终得到了0xbf5c27,在gbk中,它是一个两个字符的序列:0xbf5c,后面是0x27。或者换言之,一个有效字符,后跟一个未转义的'。但我们没有使用addslashes()。接下来…好的。
mysql_real_escape_string()好的。
对mysql_real_escape_string()的C API调用与addslashes()的不同之处在于它知道连接字符集。因此,它可以为服务器期望的字符集正确地执行转义。然而,到目前为止,客户认为我们仍在使用latin1进行连接,因为我们从未告诉过它。我们确实告诉服务器我们使用的是gbk,但客户机仍然认为它是latin1。好的。
因此,对mysql_real_escape_string()的调用插入了反斜杠,我们的"转义"内容中有一个自由挂起的'字符!事实上,如果我们看gbk字符集中的$var,我们会看到:好的。
这正是攻击所需要的。好的。
查询好的。
这部分只是一个形式,但这里是呈现的查询:好的。
1
| SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1 |
恭喜你,你刚刚用mysql_real_escape_string()成功地攻击了一个程序…好的。坏的
情况变得更糟了。PDO默认使用mysql模拟已准备好的语句。这意味着在客户端,它基本上通过mysql_real_escape_string()执行sprintf(在C库中),这意味着以下操作将导致成功注入:好的。
1 2 3
| $pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*")); |
现在,值得注意的是,您可以通过禁用模拟的准备语句来防止这种情况:好的。
1
| $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); |
这通常会导致一个真正准备好的语句(即,数据从查询中以单独的数据包发送)。但是,请注意,PDO会悄悄地回退到模仿MySQL本机无法准备的语句:那些可以在手册中列出的语句,但是要注意选择适当的服务器版本)。好的。丑陋的
我一开始就说过,如果我们使用mysql_set_charset('gbk')而不是SET NAMES gbk,我们就可以避免所有这些。如果您从2006年开始使用MySQL版本,这是真的。好的。
如果您使用的是早期的MySQL版本,那么mysql_real_escape_string()中的一个bug意味着,为了进行转义,将有效负载中的无效多字节字符(如那些)视为单字节,即使客户机已经正确地了解了连接编码,因此此攻击仍然会成功。在MySQL4.1.20、5.0.22和5.1.11中修复了这个bug。好的。
但最糟糕的是,在5.3.6之前,PDO没有公开mysql_set_charset()的C API,所以在以前的版本中,它不能阻止对每个可能的命令进行攻击!它现在作为DSN参数公开。好的。拯救恩典
正如我们在开始时所说,要使此攻击起作用,必须使用易受攻击的字符集对数据库连接进行编码。utf8mb4不易受攻击,但可以支持每个Unicode字符:因此您可以选择使用它来代替—但它只有在MySQL5.5.3之后才可用。另一种选择是utf8,它也不易受攻击,可以支持整个Unicode基本多语言平面。好的。
或者,您可以启用NO_BACKSLASH_ESCAPESSQL模式,它(除其他外)会改变mysql_real_escape_string()的操作。启用此模式后,0x27将替换为0x2727,而不是0x5c27,因此转义过程无法在以前不存在的任何易受攻击的编码中创建有效字符(即0xbf27仍然是0xbf27等);mdash;因此服务器仍将拒绝将字符串视为无效。但是,请参阅@eggyal的答案,了解使用此SQL模式可能产生的不同漏洞。好的。安全实例
以下示例是安全的:好的。
因为服务器正在等待utf8…好的。
因为我们已经正确地设置了字符集,以便客户机和服务器匹配。好的。
1 2 3 4
| $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 /*")); |
因为我们关闭了模拟的准备语句。好的。
1 2 3
| $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 /*")); |
因为我们已经正确设置了字符集。好的。
1 2 3 4 5
| $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一直在做真实的准备语句。好的。包扎
如果你:好的。
- 使用现代版本的mysql(最新版本5.1,所有版本5.5、5.6等)和mysql_set_charset()/$mysqli->set_charset()/pdo的dsn charset参数(在php≥5.3.6中)
或好的。
- 不要使用易受攻击的字符集进行连接编码(只使用utf8/latin1/ascii等)
你100%安全。好的。
否则,即使你使用的是mysql_real_escape_string(),你也很脆弱…好的。好啊。
- PDO EMURATING为MYSQL准备发言,真的吗?I don't see any reason why i t would do that since the driver supports i t natively.不是吗?
- It does.他们在文件中说,它不存在,但在源代码中,它是表面可见的,便于五年。I chack it up to incidence of the Devs.
- @Theodoror.Smith:it's not that easy to fix.I've been working on changing the default,but it fails a boat load of tests when switched.So it's a bigger change than it seems.我还是希望能用5.5来完成它
- @Ircmaxell there are few things:as stated in the article you link to,this vulnerability could be used only when using EDOCX1 biblic 7 and not EDOCX1 universal 8-that should be safe,as written in the article(read once more).Secondly I am very curious how could one put,get or post EDOCX1 penal 9 followed by EDOCX1 pental 10…though it could be demonstrated with ourselves written php i doubt one could post such values from a form or an URL…
- @Shadyyyx:No,the vulnerability the article described was about EDOCX1 classic 11.I based this vulnerability on that one.试试看。Go get mysql 5.0,and run this exploit and see for yourself.As far as how to put that into put/get/post,it's little.输入的数据只是Byte Streams。字母名称9只是一个容易理解的方式产生一个字节。I've demoted this vulnerability live in front of multiple conferences.请相信我…但是,如果你不,尝试它自己。It works…
- @Shadyyyx:As for passing such funkiness in$@EDOCX1 13 URL,EDOCX1,14 commency in the code,and Bob's your uncle.
- +1 for spanking php_devs
- @Markamery:On reflection,I think you're right.如果我想把EDOCX1的字母表15(英文)I.E.EDOCX1的字母表16(英文)超过EDOCX1的字母表17However,if the server i s decoming received bytes using GBK(I.E.the client was mistaken about the connection's encouraging)they will perceive there to be an UNESCAP EDOCX1 individual 20.and Cannot have any way of knowing that wasn't the developer's intended sql.I have updated this answer with what I think had been intended:prior to the bugfix,even correctly setting the client charset would uldn't help!
- @Eggyal我现在正确地理解了这个问题(并且为了好玩而复制了它)。谢谢你在这里的出色工作。其他值得注意的事项:1.同样的攻击也可以用mysqli_real_escape_string来演示(正如人们所期望的那样);你在这里对mysqli的简要说明可以被一些愚蠢的人解释为mysqli对这种攻击免疫,但只有使用参数化查询时才是这样。2。php.net/manual/en/mysqlinfo.concepts.charset.php提示此攻击的存在,并描述为每个API设置连接字符集的正确方法。
- 我有一种感觉,一些强调应该被删除,一个头脑冷静的TL;Dr补充说,只要编码设置正确并且使用单引号,就不会有任何问题。
- @用户1986811不,换个方向。该查询是漏洞的来源…切勿使用set names。相反,总是调用适当的API方法(对于mysql*:mysql_set_charset())。
- 好吧,然后我很困惑……我会删除我的愚蠢评论,非常感谢。有一刻……为什么在安全的例子下特别如此
- 字符集仍然是一个问题吗?因为在php-5.6.1标签上,mysql_real_escape_strings的描述说明Escape special characters in a string for use in a SQL statement, taking into account the current charset of the connection所以我想它取决于如何确定"连接的当前字符集"。
考虑以下查询:
mysql_real_escape_string()不会保护你不受伤害。在查询中的变量周围使用单引号(' ')是防止这种情况发生的原因。以下也是一个选项:
1 2
| $iId = (int)"1 OR 1=1";
$sSql ="SELECT * FROM table WHERE id = $iId"; |
- 如果用户传递一个单引号作为值的一部分:1'; DROP TABLE --,后面的注释将使引擎忽略语句中悬空的其他单引号
- @wesley $iId = mysql_real_escape_string((int)"1; DROP table");或`$dirty="1;drop table";$iid=mysql_real_escape_string((int)$dirty);我认为比你的例子更好。
- 但这不是真正的问题,因为mysql_query()不执行多个语句,不是吗?
- @Pekka,although the usual example is EDOCX1 commercial 0,in practice the attacker is more likely to EDOCX1.在Latter案中,第二个选择通常是通过使用一个字母2的
- EDOCX1音乐剧3-This makes no sense.It doesn't differ from EDOCX1 symposium 4 at all.And they will produce the same result for every input
- 这不仅是对职能的滥用,而且是对任何其他职能的滥用。在所有版本中查看在圣经阅读器中查看It's not mean to be used with integer fields.
- @Ircmaxell,yet the answer is totally misleading.Obviously the question is asking about the contents within the quotes."What are not there"is not the answer to this question.
- 你为什么要删除剪辑,@Eggyal?选择一个国际生产一个数字,Anyway。There can be nothing dangerous about numbers related to escaping,right?and cast to integer itself does not care about any escaping.
- @Wesley"The fact that you use single quotes(……)around your variables inside your r query is what protects you against this."Well but the single quotes were part of the example,the question asked for an attack for that situation,when you use single quotes.
- Wesley van Opdorp Why does single quoting protect from sql injection?The query look like this after expanding variables:select*from table where login="a a a"or 1=1--'and password='some value'why this is not a valid sql statement?
- @curiousguy the query would look like ` select*from table where login='aaa or 1=1--'and password=` some'uu value';your payload value is the whole term and single quotes are put around the whole term,thus it is safe.
TL;DR
mysql_real_escape_string() will provide no protection whatsoever (and could furthermore munge your data) if:
Ok.
-
MySQL's NO_BACKSLASH_ESCAPES SQL mode is enabled (which it might be, unless you explicitly select another SQL mode every time you connect); and
Ok.
-
your SQL string literals are quoted using double-quote " characters.
Ok.
This was filed as bug #72458 and has been fixed in MySQL v5.7.6 (see the section headed"The Saving Grace", below).
Ok.
这是另一个(也许更少?)模糊边缘的情况!!!!
为了向@ircmaxell出色的回答致敬(实际上,这应该是恭维而不是剽窃!),我将采用他的格式:好的。进攻
从演示开始…好的。
这将返回test表中的所有记录。解剖:好的。
选择SQL模式好的。
如字符串文字所述:好的。
There are several ways to include quote characters within a string:
Ok.
-
A"'" inside a string quoted with"'" may be written as"''".
Ok.
-
A""" inside a string quoted with""" may be written as"""".
Ok.
-
Precede the quote character by an escape character ("\").
Ok.
-
A"'" inside a string quoted with""" needs no special treatment and need not be doubled or escaped. In the same way,""" inside a string quoted with"'" needs no special treatment.
Ok.
如果服务器的SQL模式包括NO_BACKSLASH_ESCAPES,则这些选项中的第三个选项—是mysql_real_escape_string()通常采用的方法—不可用:必须使用前两个选项之一。请注意,第四个项目符号的效果是必须知道将用于引用文字的字符,以避免咀嚼数据。好的。
有效载荷好的。
有效载荷以"的特征启动了这一注入。没有特定的编码。没有特殊字符。没有奇怪的字节。好的。
mysql_real_escape_string()好的。
幸运的是,mysql_real_escape_string()确实检查了SQL模式并相应地调整了它的行为。见libmysql.c:好的。
1 2 3 4 5 6 7 8
| ulong STDCALL
mysql_real_escape_string(MYSQL *mysql, char *to ,const char *from ,
ulong length )
{
if (mysql ->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES )
return escape_quotes_for_mysql (mysql ->charset, to , 0, from , length );
return escape_string_for_mysql (mysql ->charset, to , 0, from , length );
} |
因此,如果使用NO_BACKSLASH_ESCAPESSQL模式,则调用不同的底层函数escape_quotes_for_mysql()。如上所述,这样的函数需要知道将使用哪个字符来引用文本,以便在不导致另一个引用字符从字面上重复的情况下重复它。好的。
但是,此函数任意假定字符串将使用单引号'字符进行引用。见charset.c:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| /*
Escape apostrophes by doubling them up
// [ deletia 839-845 ]
DESCRIPTION
This escapes the contents of a string by doubling up any apostrophes that
it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
effect on the server.
// [ deletia 852-858 ]
*/
size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
char *to, size_t to_length,
const char *from, size_t length)
{
// [ deletia 865-892 ]
if (*from == '\'')
{
if (to + 2 > to_end)
{
overflow= TRUE;
break;
}
*to++= '\'';
*to++= '\'';
} |
因此,它使双引号"字符保持不变(并使所有单引号'字符加倍),而不考虑用于引用文字的实际字符!在我们的例子中,$var与提供给mysql_real_escape_string()的论点完全相同,就好像根本没有发生任何逃避。好的。
查询好的。
1
| mysql_query('SELECT * FROM test WHERE name ="'.$var.'" LIMIT 1'); |
某种形式上,呈现的查询是:好的。
1
| SELECT * FROM test WHERE name ="" OR 1=1 --" LIMIT 1 |
正如我那位博学的朋友所说:恭喜你,你刚刚成功地攻击了一个使用mysql_real_escape_string()的程序…好的。坏的
mysql_set_charset()无法帮助,因为这与字符集无关;mysqli::real_escape_string()也无法帮助,因为这只是围绕同一个函数的不同包装器。好的。
问题是,如果还不明显的话,对mysql_real_escape_string()的调用无法知道该文本将用哪个字符引用,这将留给开发人员稍后决定。因此,在NO_BACKSLASH_ESCAPES模式下,从字面上看,此函数无法安全地转义每个输入以用于任意引用(至少,不需要将不需要加倍的字符加倍,从而咀嚼数据)。好的。丑陋的
情况越来越糟。NO_BACKSLASH_ESCAPES在野外可能并不少见,因为它必须用于与标准SQL兼容(例如,见SQL-92规范第5.3节,即 ::=
语法生成,并且没有反斜杠的任何特殊含义)。此外,它的使用被明确推荐为解决ircmaxell的文章描述的(长期以来修复的)bug的一种方法。谁知道呢,有些DBA甚至可能将其配置为默认打开,以阻止使用不正确的转义方法,如addslashes()。好的。
另外,新连接的SQL模式是由服务器根据其配置设置的(SUPER用户可以随时更改);因此,为了确定服务器的行为,必须始终在连接后明确指定所需的模式。好的。拯救恩典
只要您总是明确设置SQL模式不包含NO_BACKSLASH_ESCAPES,或者使用单引号字符引用mysql字符串文字,这个bug就不能隐藏其丑陋的头部:分别不使用escape_quotes_for_mysql(),或者它对哪些引号字符需要重复的假设是正确的。好的。
因此,我建议任何使用NO_BACKSLASH_ESCAPES的人也启用ANSI_QUOTES模式,因为它将强制习惯性使用单引号字符串。请注意,这不会防止在使用双引号文本的情况下插入SQL,它只会降低发生这种情况的可能性(因为正常的非恶意查询会失败)。好的。
在pdo中,它的等效函数PDO::quote()和它的准备好的语句仿真器都调用mysql_handle_quoter()—这正好做到了:它确保转义的文本以单引号引用,因此您可以确定pdo始终不受此错误的影响。好的。
从MySQLv5.7.6开始,这个bug已经修复。查看更改日志:好的。
Functionality Added or Changed
-
Incompatible Change: A new C API function, mysql_real_escape_string_quote(), has been implemented as a replacement for mysql_real_escape_string() because the latter function can fail to properly encode characters when the NO_BACKSLASH_ESCAPES SQL mode is enabled. In this case, mysql_real_escape_string() cannot escape quote characters except by doubling them, and to do this properly, it must know more information about the quoting context than is available. mysql_real_escape_string_quote() takes an extra argument for specifying the quoting context. For usage details, see mysql_real_escape_string_quote().
Ok.
Note
Applications should be modified to use mysql_real_escape_string_quote(), instead of mysql_real_escape_string(), which now fails and produces an CR_INSECURE_API_ERR error if NO_BACKSLASH_ESCAPES is enabled.
Ok.
参考文献:另见Bug 19211994。好的。
< /块引用>安全实例
结合ircmaxell解释的bug,以下示例是完全安全的(假设使用MySQL的时间晚于4.1.20、5.0.22、5.1.11;或者不使用gbk/big5连接编码):好的。
…因为我们已经明确选择了一个不包括NO_BACKSLASH_ESCAPES的SQL模式。好的。
…因为我们用单引号引用字符串文字。好的。
1 2
| $stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]); |
…因为PDO准备好的语句不受此漏洞的影响(如果您使用的是php≥5.3.6并且在dsn中正确设置了字符集,或者准备好的语句仿真已被禁用,那么ircmaxell也不受此漏洞的影响)。好的。
1 2
| $var = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1"); |
…因为pdo的quote()函数不仅转义了文字,而且还引用了文字(在单引号'字符中);注意,为了避免出现ircmaxell的错误,您必须使用php≥5.3.6并正确设置dsn中的字符集。好的。
1 2 3 4
| $stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param ="' OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute(); |
…因为mysqli准备的语句是安全的。好的。包扎
因此,如果你:好的。
或好的。
或好的。
…那么您应该是完全安全的(字符串转义范围之外的漏洞)。好的。好啊。
- 因此,tl;dr将类似于"没有反斜线转义mysql服务器模式,如果不使用单引号,则可能导致注入。
- @你的朋友:是的,谢谢!我再补充一点。
- 我无法访问bugs.mysql.com/bug.php?ID=72458;我刚得到一个拒绝访问的页面。它是否因为安全问题而对公众隐藏?另外,我是否正确地从这个答案中理解到,你是脆弱性的发现者?如果是,祝贺你。
- @Markamery:呃,我认为这两个问题的答案都是"是的"——虽然我怀疑很多人不会认为这本身就是一个漏洞,而是一个分离转义和引用的设计缺陷。在bug报告中,我提出了一个修正,但是当它改变了协议(尽管是以一种安全的方式)时,我不知道该修正是否会被采用。
- 首先,人们不应该使用"作为字符串。SQL说这是用于标识符的。但是,嗯……这只是MySQL的另一个例子,它说"螺丝钉标准,我愿意做任何我想做的事情"。(幸运的是,您可以将ANSI_QUOTES包括在修复报价中断的模式中。然而,公开无视标准是一个更大的问题,可能需要更严厉的措施。)
- @eggyal,使用pdo是否足够,或者是否有必要使用pdo准备的语句?在php/pdo中考虑这一点:$sql="select*from product_master,其中abbr='.$u get['prod']";//order by skey limit".mt_rand(1,10.)",10";$stmt=$pdo->query($sql);这将是一个非常开放的注入方式,对$_get没有保护。不?另外,mysqli_real_escape_字符串需要支持单引号的使用,对吗?如果这些问题看起来太明显,我的问题是我正在阅读答案,我不确定它是否说得很清楚。我可以看到例子显示了这一点。
- @丹艾伦:我的回答有点宽泛,因为您可以通过PDO的quote()函数避免这个特定的错误,但是准备好的语句是一种更安全和更合适的方法,可以避免一般的注入。当然,如果您已经将未捕获的变量直接连接到SQL中,那么无论以后使用什么方法,您都很容易受到注入的攻击。
- @Eggyall:我们的系统依赖于上面的第二个安全示例。有错误,其中mysql_real_escape_字符串被省略。在紧急模式下修复它们似乎是一条谨慎的道路,希望在修正之前我们不会受到撞击。我的基本原理是转换为准备好的陈述将是一个更长的过程,这将不得不在之后。准备好的声明更安全的原因是错误不会造成漏洞吗?换句话说,是否正确实现了上面的第二个示例与准备好的语句一样安全?
- 很好的故障……我假设mysql->charset在函数escape_quotes_for_mysql(mysql->charset, to, 0, from, length)和escape_string_for_mysql(mysql->charset, to, 0, from, length);中是mysql的默认连接字符集,或者如果使用SET NAMES 或anny类型的set_charset()函数?
- @Eggyal,如果我设置sql_mode=ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_BACKSLASH_ESCAPES.&zwnj;&误8203;这样安全吗?是否需要在PHP脚本中额外使用mysql_query("SET SQL_MODE=''");?
好吧,除了%通配符之外,没有什么可以通过它。如果您使用LIKE语句,因为攻击者可以将%作为登录名,如果您不过滤掉它,这可能是危险的,并且必须暴力地输入任何用户的密码。人们经常建议使用准备好的语句使其100%安全,因为数据不会以这种方式干扰查询本身。但是对于这样简单的查询,像$login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);这样的操作可能更有效。
- +1,但通配符用于LIKE子句,而不是简单的相等。
- 与使用准备好的语句相比,您认为简单的替换方法more efficient是什么?(准备好的语句总是可以工作的,库可以在发生攻击时快速更正,不会暴露人为错误[例如错误地键入完整的替换字符串],如果重新使用该语句,则具有显著的性能优势。)
- @斯拉瓦:你实际上只把用户名和密码限制在word-chars。大多数了解安全性的人都会认为这是个坏主意,因为它会大大缩小搜索空间。当然,他们也会认为在数据库中存储明文密码是个坏主意,但我们不需要使问题复杂化。:)
- @赵,我的建议只涉及登录。很明显,你不需要过滤密码,对不起,我的回答中没有明确说明。但实际上这可能是个好主意。使用"石头无知的树空间"而不是难以记住,并键入"A4&252;UA3!@V"&228;90;8F"将更加难以野蛮。即使使用一本字典,比如说3000个单词来帮助你,知道你只使用了4个单词——这仍然是大约3.3*10^12的组合。:)
- @斯拉瓦:我以前见过这个想法;见xkcd.com/936。问题是,数学并不能很好地证明这一点。您的示例17字符密码有96^17种可能,如果您忘记了umlauts并将自己限制为可打印的ascii。大约是4.5x10^33。我们说的是比暴力还要多十亿倍的工作。即使是一个8字符的ASCII密码也有7.2x10^15的可能性——是3千倍。
- @赵,很抱歉在这里继续这场神圣的战争,但是…假设你的资源很好,可以每秒尝试1亿次密码。使用3K单词字典,并且知道有4个单词尝试3.3万亿的密码需要23天。这足以让主机注意到被盗的数据库并更改您的密码。足够安全。您可以通过交替使用小写/大写字母来增加难度。假设攻击者知道有4个单词,这是不可能的。
- 如果你使用的是自造词,那么攻击者就需要按字符进行尝试…22个字符,6位/char(小写、破折号等=大约64)~=5.4双精度=年
- @斯拉瓦:大多数网站所有者甚至都不知道有服务器日志,更不用说读它们了。而一个非白痴攻击者不会窃取数据,只是简单地复制它……让站点正常运行,而您的普通所有者甚至没有意识到存在入侵(因此,不知道重置密码),给攻击者很多时间破解简单的短语。人性有效地保证了词组的简单性,尤其是在字典里允许使用单词的情况下。如果没有字典里的单词,你所做的就是需要一个20多个字符的密码,用户会因此而讨厌你。:)
- 用户只想把事情办好。在大多数情况下,安全性直接与之相反,您可以安全地假设它将以任何可能的方式被避免或破坏。人们不会随便挑选六个假想词;他们会从他们最喜欢的书或电影或其他什么东西中挑选一个短句/句子/流行语,使这个短语更容易被预测。为了防止他们做这样的事情,你基本上需要让服务器理解英语和/或搜索一个最著名的艺术作品数据库来查找所使用的短语。