ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 防御 ## **magic\_quotes\_sybase** 作用:影响 addslashes() 函数。 PHP 5.3.0 起*废弃*并将自 PHP 5.4.0 起*移除* magic\_quotes\_sybase=0 时(默认),在启用magic\_quotes\_gpc或magic\_quotes\_runtime时addslashes 将对 ' " \\ 进行 \\ 转义操作; magic\_quotes\_sybase=1 时,在启用magic\_quotes\_gpc或magic\_quotes\_runtime时addslashes 将对`'`转义成两个单引号`''`。 > 注意,当magic\_quotes\_sybase=On时,它完全覆盖了magic\_quotes\_gpc。在这种情况下,即使启用了magic\_quotes\_gpc,也不会转义双引号、反斜杠或NUL ``` <pre class="calibre10">``` <span class="token">//http://www.test.com/?name='</span> echo $_GET<span class="token3">[</span><span class="token2">'name'</span><span class="token3">]</span><span class="token3">;</span><span class="token">//''</span> <span class="token">//将会覆盖magic_quotes_gpc的设置,注意'转义为'' \和"没有转义</span> <span class="token">//http://www.test.com/?name='@"@\</span> <span class="token">//http://www.test.com/?name=%27@%22@\</span> echo $_GET<span class="token3">[</span><span class="token2">'name'</span><span class="token3">]</span><span class="token3">;</span><span class="token">//''@"@\</span> ``` ``` ## **magic\_quotes\_gpc** 作用:当php.ini中magic\_quotes\_gpc=on时,对php服务器端接收的 GET POST COOKIE 的值执行 addslashes() 操作即自动转换`'和\和"和NULL`(php5.4移除此选项) 作用范围是:WEB客户服务端。 作用时间:请求开始时,例如当脚本运行时。 `get_magic_quotes_gpc()`可以获取当前 magic\_quotes\_gpc 的配置选项设置(5.4移除后始终返回false) ``` <pre class="calibre10">``` <span class="token">//http://www.test.com/?name='@"@\</span> <span class="token">//http://www.test.com/?name=%27@%22@\</span> echo $_GET<span class="token3">[</span><span class="token2">'name'</span><span class="token3">]</span><span class="token3">;</span><span class="token">//\'@\"@\\</span> <span class="token">//注意不会转义QUERY_STRING php4是可以转义_SERVER的php5取消了,所以我们需要addslashes()转义 $_SERVER</span> echo $_SERVER<span class="token3">[</span><span class="token2">'QUERY_STRING'</span><span class="token3">]</span><span class="token3">;</span><span class="token">//name=%27@%22@\</span> ``` ``` ## **magic\_quotes\_runtime** 作用:对通过 fread()、file\_get\_contents() 返回的文本执行 addslashes() 操作,对执行sql查询的结果执行 addslashes() 操作。 自 PHP 5.3.0 起*废弃*并将自 PHP 5.4.0 起*移除* 作用范围:从文件中读取的数据或执行 exec() 的结果或是从SQL查询中得到的。 作用时间:每次当脚本访问运行状态中产生的数据。 `get_magic_quotes_runtime()`可以获取magic\_quotes\_runtime配置的值 `set_magic_quotes_runtime()`可以在脚本中设置magic\_quotes\_runtime配置的值 如果启用了`magic_quotes_runtime`,大多数返回任何形式外部数据的函数,包括数据库和文本段将会用反斜线转义引号。 如果启用了[magic\_quotes\_sybase](https://www.php.net/manual/zh/sybase.configuration.php#ini.magic-quotes-sybase),单引号会被单引号转义而不是反斜线 ``` <pre class="calibre10">``` 受 magic_quotes_runtime 影响的函数(不包括 PECL 里的函数): <span class="token4">get_meta_tags</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">file_get_contents</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">file</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">fgets</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">fwrite</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">fread</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">fputcsv</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">stream_socket_recvfrom</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">exec</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">system</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">passthru</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">stream_get_contents</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">bzread</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">gzfile</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">gzgets</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">gzwrite</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">gzread</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">exif_read_data</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">dba_insert</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">dba_replace</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">dba_fetch</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">ibase_fetch_row</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">ibase_fetch_assoc</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">ibase_fetch_object</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mssql_fetch_row</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mssql_fetch_object</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mssql_fetch_array</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mssql_fetch_assoc</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mysqli_fetch_row</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mysqli_fetch_array</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mysqli_fetch_assoc</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">mysqli_fetch_object</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_fetch_row</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_fetch_assoc</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_fetch_array</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_fetch_object</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_fetch_all</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">pg_select</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">sybase_fetch_object</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">sybase_fetch_array</span><span class="token3">(</span><span class="token3">)</span> <span class="token4">sybase_fetch_assoc</span><span class="token3">(</span><span class="token3">)</span> SplFileObject<span class="token3">:</span><span class="token3">:</span><span class="token4">fgets</span><span class="token3">(</span><span class="token3">)</span> SplFileObject<span class="token3">:</span><span class="token3">:</span><span class="token4">fgetcsv</span><span class="token3">(</span><span class="token3">)</span> SplFileObject<span class="token3">:</span><span class="token3">:</span><span class="token4">fwrite</span><span class="token3">(</span><span class="token3">)</span> ``` ``` ``` <pre class="calibre10">``` <span class="token">// 如果启用了魔术引号 即magic_quotes_gpc=on时</span> echo $_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">;</span> <span class="token">// O\'reilly </span> echo <span class="token4">addslashes</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> <span class="token">// O\\\'reilly</span> ``` ``` 由于5.4移除了magic\_quotes\_gpc配置get\_magic\_quotes\_gpc始终返回false ``` <pre class="calibre10">``` <span class="token">// 适用各个 PHP 版本的用法</span> <span class="token5">if</span> <span class="token3">(</span><span class="token4">get_magic_quotes_gpc</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span> <span class="token3">{</span> <span class="token">//删除由 addslashes() 函数添加的反斜杠</span> $lastname <span class="token1">=</span> <span class="token4">stripslashes</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> <span class="token3">}</span> <span class="token5">else</span> <span class="token3">{</span> $lastname <span class="token1">=</span> $_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">;</span> <span class="token3">}</span> ``` ``` 如果使用 MySQL ``` <pre class="calibre10">``` <span class="token5">if</span> <span class="token3">(</span><span class="token4">get_magic_quotes_gpc</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span> <span class="token3">{</span> <span class="token">//删除由 addslashes() 函数添加的反斜杠</span> $lastname <span class="token1">=</span> <span class="token4">stripslashes</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> <span class="token3">}</span> <span class="token5">else</span> <span class="token3">{</span> $lastname <span class="token1">=</span> $_POST<span class="token3">[</span><span class="token2">'lastname'</span><span class="token3">]</span><span class="token3">;</span> <span class="token3">}</span> $lastname <span class="token1">=</span> <span class="token4">mysql_real_escape_string</span><span class="token3">(</span>$lastname<span class="token3">)</span><span class="token3">;</span> echo $lastname<span class="token3">;</span> <span class="token">// O\'reilly</span> $sql <span class="token1">=</span> <span class="token2">"INSERT INTO lastnames (lastname) VALUES ('$lastname')"</span><span class="token3">;</span> ``` ``` **数据库字符集设为GBK时,0xbf27本身不是一个有效的GBK字符,但经过 addslashes() 转换后变为0xbf5c27,前面的0xbf5c是个有效的GBK字符,所以0xbf5c27会被当作一个字符0xbf5c和一个单引号来处理,结果漏洞就触发了** mysql\_real\_escape\_string() 也存在相同的问题,只不过相比 addslashes() 它考虑到了用什么字符集来处理,因此可以用相应的字符集来处理字符 当mysql\_real\_escape\_string检测到的编码方式跟client设置的编码方式(big5/bgk)不一致时,mysql\_real\_escape\_string跟addslashes是没有区别的。比如: ``` <pre class="calibre10">``` <span class="token3">[</span>client<span class="token3">]</span> default<span class="token1">-</span>character<span class="token1">-</span>set<span class="token1">=</span>latin1 <span class="token4">mysql_query</span><span class="token3">(</span><span class="token2">"SET CHARACTER SET 'gbk'"</span><span class="token3">,</span> $mysql_conn<span class="token3">)</span><span class="token3">;</span> 这种情况下mysql_real_escape_string 是基于 latin1工作的,是不安全的 <span class="token3">[</span>client<span class="token3">]</span> default<span class="token1">-</span>character<span class="token1">-</span>set<span class="token1">=</span>gbk <span class="token4">mysql_query</span><span class="token3">(</span><span class="token2">"SET CHARACTER SET 'gbk'"</span><span class="token3">,</span> $mysql_conn<span class="token3">)</span><span class="token3">;</span> 这种情况下,mysql_real_escape_string 基于 gbk 工作,是正常的 ``` ``` 实例: ``` <pre class="calibre10">``` echo <span class="token2">"PHP version: "</span><span class="token3">.</span>PHP_VERSION<span class="token3">.</span><span class="token2">"\n"</span><span class="token3">;</span><span class="token">//PHP version: 5.2.5</span> <span class="token4">mysql_connect</span><span class="token3">(</span><span class="token2">'servername'</span><span class="token3">,</span><span class="token2">'username'</span><span class="token3">,</span><span class="token2">'password'</span><span class="token3">)</span><span class="token3">;</span> <span class="token4">mysql_select_db</span><span class="token3">(</span><span class="token2">"test"</span><span class="token3">)</span><span class="token3">;</span> <span class="token4">mysql_query</span><span class="token3">(</span><span class="token2">"SET NAMES GBK"</span><span class="token3">)</span><span class="token3">;</span> <span class="token">//使用cahr处理转换后的0xbf5c27字符 chr(0xbf)为¿ chr(0x27)'</span> <span class="token">//模拟$_POST数据 ¿' OR username =username #</span> $_POST<span class="token3">[</span><span class="token2">'username'</span><span class="token3">]</span> <span class="token1">=</span> <span class="token4">chr</span><span class="token3">(</span><span class="token6">0xbf</span><span class="token3">)</span><span class="token3">.</span><span class="token4">chr</span><span class="token3">(</span><span class="token6">0x27</span><span class="token3">)</span><span class="token3">.</span><span class="token2">' OR username = username /*'</span><span class="token3">;</span> $_POST<span class="token3">[</span><span class="token2">'username'</span><span class="token3">]</span> <span class="token1">=</span> <span class="token4">chr</span><span class="token3">(</span><span class="token6">0xbf</span><span class="token3">)</span><span class="token3">.</span><span class="token4">chr</span><span class="token3">(</span><span class="token6">0x27</span><span class="token3">)</span><span class="token3">.</span><span class="token2">' OR username = username #'</span><span class="token3">;</span> $_POST<span class="token3">[</span><span class="token2">'password'</span><span class="token3">]</span> <span class="token1">=</span> <span class="token2">'guess'</span><span class="token3">;</span> $username <span class="token1">=</span> <span class="token4">addslashes</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'username'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $password <span class="token1">=</span> <span class="token4">addslashes</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'password'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $sql <span class="token1">=</span> <span class="token2">"SELECT * FROM users WHERE username = '$username' AND password = '$password'"</span><span class="token3">;</span> $result <span class="token1">=</span> <span class="token4">mysql_query</span><span class="token3">(</span>$sql<span class="token3">)</span> or <span class="token4">trigger_error</span><span class="token3">(</span><span class="token4">mysql_error</span><span class="token3">(</span><span class="token3">)</span><span class="token3">.</span>$sql<span class="token3">)</span><span class="token3">;</span> <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_num_rows</span><span class="token3">(</span>$result<span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token">//int(3) 有结果有注入风险</span> <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_client_encoding</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token1">/</span><span class="token1">/</span>latin1 $username <span class="token1">=</span> <span class="token4">mysql_real_escape_string</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'username'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $password <span class="token1">=</span> <span class="token4">mysql_real_escape_string</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'password'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $sql <span class="token1">=</span> <span class="token2">"SELECT * FROM users WHERE username = '$username' AND password = '$password'"</span><span class="token3">;</span> $result <span class="token1">=</span> <span class="token4">mysql_query</span><span class="token3">(</span>$sql<span class="token3">)</span> or <span class="token4">trigger_error</span><span class="token3">(</span><span class="token4">mysql_error</span><span class="token3">(</span><span class="token3">)</span><span class="token3">.</span>$sql<span class="token3">)</span><span class="token3">;</span> <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_num_rows</span><span class="token3">(</span>$result<span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token1">/</span><span class="token1">/</span><span class="token4">int</span><span class="token3">(</span><span class="token6">3</span><span class="token3">)</span> 有结果有注入风险 <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_client_encoding</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token1">/</span><span class="token1">/</span>latin1 <span class="token4">mysql_set_charset</span><span class="token3">(</span><span class="token2">"GBK"</span><span class="token3">)</span><span class="token3">;</span> $username <span class="token1">=</span> <span class="token4">mysql_real_escape_string</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'username'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $password <span class="token1">=</span> <span class="token4">mysql_real_escape_string</span><span class="token3">(</span>$_POST<span class="token3">[</span><span class="token2">'password'</span><span class="token3">]</span><span class="token3">)</span><span class="token3">;</span> $sql <span class="token1">=</span> <span class="token2">"SELECT * FROM users WHERE username = '$username' AND password = '$password'"</span><span class="token3">;</span> $result <span class="token1">=</span> <span class="token4">mysql_query</span><span class="token3">(</span>$sql<span class="token3">)</span> or <span class="token4">trigger_error</span><span class="token3">(</span><span class="token4">mysql_error</span><span class="token3">(</span><span class="token3">)</span><span class="token3">.</span>$sql<span class="token3">)</span><span class="token3">;</span> <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_num_rows</span><span class="token3">(</span>$result<span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token1">/</span><span class="token1">/</span><span class="token4">int</span><span class="token3">(</span><span class="token6">0</span><span class="token3">)</span> <span class="token4">var_dump</span><span class="token3">(</span><span class="token4">mysql_client_encoding</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span><span class="token3">;</span><span class="token1">/</span><span class="token1">/</span><span class="token4">string</span><span class="token3">(</span><span class="token6">3</span><span class="token3">)</span> <span class="token2">"gbk"</span> 结果: PHP version<span class="token3">:</span> <span class="token6">5.2</span><span class="token6">.5</span> <span class="token4">int</span><span class="token3">(</span><span class="token6">3</span><span class="token3">)</span> <span class="token4">string</span><span class="token3">(</span><span class="token6">6</span><span class="token3">)</span> <span class="token2">"latin1"</span> <span class="token4">int</span><span class="token3">(</span><span class="token6">3</span><span class="token3">)</span> <span class="token4">string</span><span class="token3">(</span><span class="token6">6</span><span class="token3">)</span> <span class="token2">"latin1"</span> <span class="token4">int</span><span class="token3">(</span><span class="token6">0</span><span class="token3">)</span> <span class="token4">string</span><span class="token3">(</span><span class="token6">3</span><span class="token3">)</span> <span class="token2">"gbk"</span> ``` ``` 可以看出来不论是使用addslashes还是mysql\_real\_escape\_string,我都可以利用编码的漏洞来实现输入任意密码就能登录服务器的注入攻击!!!!(攻击的原理我就不多说了,感兴趣的同学可以研究下字符编码中单字节和多字节的问题) # **完美解决方案** 由上可知 mysql\_real\_escape\_string() 、 addslashes()、和str\_replace(替换单引号)是不能解决sql注入问题的 编写代码是要特别小心获取变量,如:$\_GET $\_POST $\_COOKIE $\_SERVER 完美解决sql注入的方案就是使用拥有Prepared Statement机制的PDO和MYSQLi来代替mysql\_query(注:mysql\_query自 PHP 5.5.0 起已废弃,并在将来会被移除): ``` <pre class="calibre10">``` <span class="token">//PDO:</span> $pdo <span class="token1">=</span> <span class="token5">new</span> <span class="token4">PDO</span><span class="token3">(</span><span class="token2">'mysql:dbname=dbtest;host=127.0.0.1;charset=utf8'</span><span class="token3">,</span> <span class="token2">'user'</span><span class="token3">,</span> <span class="token2">'pass'</span><span class="token3">)</span><span class="token3">;</span> $pdo<span class="token1">-</span><span class="token1">></span><span class="token4">setAttribute</span><span class="token3">(</span>PDO<span class="token3">:</span><span class="token3">:</span>ATTR_EMULATE_PREPARES<span class="token3">,</span> <span class="token6">false</span><span class="token3">)</span><span class="token3">;</span> $pdo<span class="token1">-</span><span class="token1">></span><span class="token4">setAttribute</span><span class="token3">(</span>PDO<span class="token3">:</span><span class="token3">:</span>ATTR_ERRMODE<span class="token3">,</span> PDO<span class="token3">:</span><span class="token3">:</span>ERRMODE_EXCEPTION<span class="token3">)</span><span class="token3">;</span> $stmt <span class="token1">=</span> $pdo<span class="token1">-</span><span class="token1">></span><span class="token4">prepare</span><span class="token3">(</span><span class="token2">'SELECT * FROM employees WHERE name = :name'</span><span class="token3">)</span><span class="token3">;</span> $stmt<span class="token1">-</span><span class="token1">></span><span class="token4">execute</span><span class="token3">(</span><span class="token4">array</span><span class="token3">(</span><span class="token2">'name'</span> <span class="token1">=</span><span class="token1">></span> $name<span class="token3">)</span><span class="token3">)</span><span class="token3">;</span> foreach <span class="token3">(</span>$stmt as $row<span class="token3">)</span> <span class="token3">{</span> <span class="token">// do something with $row</span> <span class="token3">}</span> <span class="token">//MYSQLi:</span> $stmt <span class="token1">=</span> $dbConnection<span class="token1">-</span><span class="token1">></span><span class="token4">prepare</span><span class="token3">(</span><span class="token2">'SELECT * FROM employees WHERE name = ?'</span><span class="token3">)</span><span class="token3">;</span> $stmt<span class="token1">-</span><span class="token1">></span><span class="token4">bind_param</span><span class="token3">(</span><span class="token2">'s'</span><span class="token3">,</span> $name<span class="token3">)</span><span class="token3">;</span> $stmt<span class="token1">-</span><span class="token1">></span><span class="token4">execute</span><span class="token3">(</span><span class="token3">)</span><span class="token3">;</span> $result <span class="token1">=</span> $stmt<span class="token1">-</span><span class="token1">></span><span class="token4">get_result</span><span class="token3">(</span><span class="token3">)</span><span class="token3">;</span> <span class="token5">while</span> <span class="token3">(</span>$row <span class="token1">=</span> $result<span class="token1">-</span><span class="token1">></span><span class="token4">fetch_assoc</span><span class="token3">(</span><span class="token3">)</span><span class="token3">)</span> <span class="token3">{</span> <span class="token">// do something with $row</span> <span class="token3">}</span> ``` ``` 坚持过滤输入和转义输出 addcslashes($input, $charlist)、mysql\_real\_escape\_string() htmlspecialchars($input)、strip\_tags($input) 富文本时使用[HTMLPurifier 富文本过滤器](http://htmlpurifier.org/) 数据库报错信息泄露防范 1. 把php.ini文件 display\_errors = Off 数据库查询函数前面加一个@字符