-
Notifications
You must be signed in to change notification settings - Fork 105
/
Copy pathdatabase.xml
395 lines (366 loc) · 17.8 KB
/
database.xml
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
<?xml version="1.0" encoding="utf-8"?>
<!-- $Revision$ -->
<!-- EN-Revision: 7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317 Maintainer: lm92 Status: ready -->
<!-- CREDITS: dallas, mowangjuanzi, Luffy -->
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>数据库安全</title>
<simpara>
今时今日,数据库系统已经成为各个动态网站上 web
应用程序的重要组成部分。由于非常敏感和机密的数据有可能保存在数据库中,所以对数据库实施保护就显得尤为重要了。
</simpara>
<simpara>
要从数据库中提取或者存入数据,就必须经过连接数据库、发送一条合法查询、获取结果、关闭连接等步骤。目前,能完成这一系列动作的最常用的查询语言是结构化查询语言
Structured Query Language (SQL)。可以看看攻击者是如何<link
linkend="security.database.sql-injection">篡改 SQL 查询语句的</link>。
</simpara>
<simpara>
PHP 本身并不能保护数据库的安全。下面的章节只是讲述怎样用 PHP
脚本对数据库进行基本的访问和操作。
</simpara>
<simpara>
记住一条简单的原则:深入防御。保护数据库的措施越多,攻击者就越难获得和使用数据库内的信息。正确地设计和应用数据库可以减少被攻击的担忧。
</simpara>
<sect1 xml:id="security.database.design">
<title>设计数据库</title>
<simpara>
第一步一般都是创建数据库,除非是使用第三方的数据库服务。当创建一个数据库的时候,会指定一个所有者来执行和新建语句。通常,只有所有者(或超级用户)才有权对数据库中的对象进行任意操作。如果想让其他用户使用,就必须赋予他们权限。
</simpara>
<simpara>
应用程序永远不要使用数据库所有者或超级用户帐号来连接数据库,因为这些帐号可以执行任意的操作,比如说修改数据库结构(例如删除一个表)或者清空整个数据库的内容。
</simpara>
<simpara>
应该为程序的每个方面创建不同的数据库帐号,并赋予对数据库对象的极有限的权限。仅分配给能完成其功能所需的权限,避免同一个用户可以完成另一个用户的事情。这样即使攻击者利用程序漏洞取得了数据库的访问权限,也最多只能做到和该程序一样的影响范围。
</simpara>
<simpara>
鼓励用户不要把所有的事务逻辑都用 web
应用程序(即用户的脚本)来实现。最好用视图(view)、触发器(trigger)或者规则(rule)在数据库层面完成。当系统升级的时候,需要为数据库开辟新的接口,这时就必须重做所有的数据库客户端。除此之外,触发器还可以透明和自动地处理字段,并在调试程序和跟踪事实时提供有用的信息。
</simpara>
</sect1>
<sect1 xml:id="security.database.connection">
<title>连接数据库</title>
<simpara>
把连接建立在 SSL 加密技术上可以增加客户端和服务器端通信的安全性,或者 SSH
也可以用于加密客户端和数据库之间的连接。如果使用了这些技术的话,攻击者要监视服务器的通信或者得到数据库的信息是很困难的。
</simpara>
<!--simpara>
If your database server has native SSL support, consider using <link
linkend="ref.openssl">OpenSSL functions</link> in communication between
PHP and database via SSL.
</simpara-->
</sect1>
<sect1 xml:id="security.database.storage">
<title>加密存储模型</title>
<simpara>
SSL/SSH 能保护客户端和服务器端交换的数据,但 SSL/SSH
并不能保护数据库中已有的数据。SSL 只是一个加密网络数据流的协议。
</simpara>
<simpara>
如果攻击者取得了直接访问数据库的许可(绕过 web
服务器),敏感数据就可能暴露或者被滥用,除非数据库自己保护了这些信息。对数据库内的数据加密是减少这类风险的有效途径,但是只有很少的数据库提供这些加密功能。
</simpara>
<simpara>
解决这个问题最简单的方法是创建自己的加密包,然后在 <acronym>PHP</acronym> 脚本中使用它。<acronym>PHP</acronym>
可以通过一些扩展来帮助你解决这个问题,比如 <link
linkend="book.openssl">OpenSSL</link> 和 <link
linkend="book.sodium">Sodium</link>,涵盖了多种加密算法。脚本在将数据插入数据库之前对其进行加密,并在检索时对其进行解密。更多关于加密工作的例子请参见参考文献。
</simpara>
<sect2 xml:id="security.database.storage.hashing">
<title>Hashing</title>
<simpara>
In the case of truly hidden data, if its raw representation is not needed
(i.e. will not be displayed), hashing should be taken into consideration.
The well-known example for hashing is storing the cryptographic hash of a
password in a database, instead of the password itself.
</simpara>
<simpara>
The <link linkend="ref.password">password</link> functions
provide a convenient way to hash sensitive data and work with these hashes.
</simpara>
<simpara>
<function>password_hash</function> is used to hash a given string using the
strongest algorithm currently available and <function>password_verify</function>
checks whether the given password matches the hash stored in database.
</simpara>
<example>
<title>Hashing password field</title>
<programlisting role="php">
<![CDATA[
<?php
// 存储密码散列
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
pg_escape_string($username),
password_hash($password, PASSWORD_DEFAULT));
$result = pg_query($connection, $query);
// 发送请求来验证用户密码
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
pg_escape_string($username));
$row = pg_fetch_assoc(pg_query($connection, $query));
if ($row && password_verify($password, $row['pwd'])) {
echo 'Welcome, ' . htmlspecialchars($username) . '!';
} else {
echo 'Authentication failed for ' . htmlspecialchars($username) . '.';
}
?>
]]>
</programlisting>
</example>
</sect2>
</sect1>
<sect1 xml:id="security.database.sql-injection">
<title>SQL 注入</title>
<simpara>
SQL 注入是一种攻击技术,攻击者利用应用程序代码中构建动态 SQL
查询的缺陷。攻击者可以访问应用程序的特权部分,从数据库检索所有信息,篡改现有数据,甚至在数据库主机上执行危险的系统级命令。当开发人员在他们的
SQL 语句中连接或插入任意输入时,这种漏洞就会发生。
</simpara>
<para>
<example>
<title>
将结果集切割成页面……并创建超级用户(PostgreSQL)。
</title>
<simpara>
在下面的示例中,用户输入直接插入到 SQL 查询中,使得攻击者能够在数据库中获得超级用户账户。
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$offset = $_GET['offset']; // 注意,没有输入验证!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
]]>
</programlisting>
</example>
普通用户会点击“上一页”、“下一页”,<varname>$offset</varname> 已经编码到 URL 的链接。脚本期望传入的 <varname>$offset</varname>
是数字。然而,如果有人尝试把以下语句追加入 URL 中的话:
<informalexample>
<programlisting role="sql">
<![CDATA[
0;
insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
select 'crack', usesysid, 't','t','crack'
from pg_shadow where usename='postgres';
--
]]>
</programlisting>
</informalexample>
如果发生,脚本将向攻击者提供超级用户访问权限。注意那个 <literal>0;</literal> 是为了向原始查询提供有效的偏移量并终止。
</para>
<note>
<para>
这是常见的技术,使用 SQL 中的注释符号 <literal>--</literal>,强制 SQL 解析器忽略开发者编写的查询的其余部分。
</para>
</note>
<para>
获取密码的一种可行方式是欺骗搜索结果页面。攻击者只需查看是否有已提交的未经适当处理变量在 SQL
语句中使用。这些过滤器通常可以在先前的表单中设置,以定制 <literal>SELECT</literal> 语句中的
<literal>WHERE、ORDER BY、LIMIT</literal> 和 <literal>OFFSET</literal> 子句。如果数据库支持
<literal>UNION</literal> 构造,攻击者可能会尝试将整个查询附加到原始查询中,以从任意表中列出密码。强烈建议仅存储密码的安全散列值,而不是密码本身。
<example>
<title>
列出文章……以及一些密码(任何数据库服务器)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
]]>
</programlisting>
</example>
查询的静态部分可以与另一个 <literal>SELECT</literal> 语句组合来显示所有密码:
<informalexample>
<programlisting role="sql">
<![CDATA[
'
union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable;
--
]]>
</programlisting>
</informalexample>
</para>
<para>
<literal>UPDATE</literal> 和 <literal>INSERT</literal> 语句也容易受到这种攻击的影响。
<example>
<title>
从重置密码……到获得更多权限(任何数据库服务器)
</title>
<programlisting role="php">
<![CDATA[
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
]]>
</programlisting>
</example>
如果恶意的用户提交值 <literal>' or uid like'%admin%</literal>
给 <varname>$uid</varname> 来改变 admin 的密码,或者简单设置
<varname>$pwd</varname> 为 <literal>hehehe', trusted=100, admin='yes</literal>
去获得更多权限,然后查询语句实际上就变成了:
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
]]>
</programlisting>
</informalexample>
</para>
<simpara>
虽然攻击者必须具备至少一些关于数据库架构的知识才能进行成功的攻击,但获取这些信息通常非常简单。例如代码可以是开源软件的一部分并且公开可用。这些信息也可能通过闭源代码泄露——即使它经过了编码、混淆或编译——甚至通过自己的代码显示错误消息来泄露。其他方法包括使用典型的
table 和列名。例如,使用“users” table 和列名“id”、“username”和“password”的登录表单。
</simpara>
<para>
<example>
<title>攻击数据库主机操作系统(MSSQL Server)</title>
<simpara>
一种可怕的示例是一些数据库主机上可以访问操作系统级别的命令。
</simpara>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
]]>
</programlisting>
</example>
如果攻击者提交
<literal>a%' exec master..xp_cmdshell 'net user test testpass /ADD' --</literal>
作为变量 <varname>$prod</varname> 的值,那么 <varname>$query</varname> 将会变成
<informalexample>
<programlisting role="php">
<![CDATA[
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$result = mssql_query($query);
?>
]]>
</programlisting>
</informalexample>
MSSQL Server 执行批处理中的 SQL 语句,其中包括向本地账户数据库添加新用户的命令。如果该应用程序以 <literal>sa</literal>
身份运行,并且 MSSQLSERVER 服务以足够的权限运行,则攻击者现在将拥有一个账户,可以用此账户访问这台机器。
</para>
<note>
<para>
以上的一些示例与特定的数据库服务器相关联,这并不意味着不能对其他产品进行类似的攻击。用户的数据库服务器可能以其他方式同样存在漏洞。
</para>
</note>
<para>
<mediaobject>
<alt>关于 SQL 注入问题的有趣示例</alt>
<imageobject>
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
</imageobject>
<caption>
<simpara>
图片由 <link xlink:href="&url.xkcd;327">xkcd</link> 提供
</simpara>
</caption>
</mediaobject>
</para>
<sect2 xml:id="security.database.avoiding">
<title>预防措施</title>
<para>
避免 SQL 注入的推荐方法是通过使用预处理语句绑定所有数据。仅仅使用参数化查询并不能完全避免 SQL 注入,但它是提供输入给
SQL 语句的最简单和最安全的方式。在 <literal>WHERE</literal>、<literal>SET</literal> 和 <literal>VALUES</literal>
子句中,所有动态数据常量都必须替换为占位符。实际数据将在执行过程中进行绑定,并与 SQL 命令分开发送。
</para>
<para>
参数绑定只能用于数据。SQL 查询的其他动态部分必须根据已知的允许值列表进行筛选。
</para>
<para>
<example>
<title>通过使用 PDO 预处理语句来避免 SQL 注入</title>
<programlisting role="php">
<![CDATA[
<?php
// The dynamic SQL part is validated against expected values
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
$productId = $_GET['productId'];
// The SQL is prepared with a placeholder
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
// The value is provided with LIKE wildcards
$stmt->execute(["%{$productId}%"]);
?>
]]>
</programlisting>
</example>
</para>
<simpara>
预处理语句由 <link linkend="pdo.prepared-statements">PDO</link>、<link linkend="mysqli.quickstart.prepared-statements">MySQLi</link> 和其他数据库库提供。
</simpara>
<simpara>
SQL 注入攻击主要是基于利用代码在编写时没有考虑安全性。永远不要相信任何输入,特别是来自客户端的输入,即使它来自于选择框、隐藏的输入字段或 cookie。第一个示例表明,即使是如此简单的查询也可能带来灾难。
</simpara>
<para>
深度防御策略涉及几种良好的编程实践:
<itemizedlist>
<listitem>
<simpara>
永远不要以超级用户或数据库所有者的身份连接到数据库。始终使用具有最低权限的自定义用户。
</simpara>
</listitem>
<listitem>
<simpara>
检查指定输入是否具有预期的数据类型。PHP 拥有很多输入验证函数,从最简单的<link linkend="ref.var">变量函数</link>和<link
linkend="ref.ctype">字符类型函数</link>(例如 <function>is_numeric</function>、<function>ctype_digit</function>)到支持
<link linkend="ref.pcre">Perl 兼容正则表达式</link>的函数。
</simpara>
</listitem>
<listitem>
<simpara>
如果应用程序期望数字输入,可以考虑使用 <function>ctype_digit</function> 验证数据,使用
<function>settype</function>更改其类型,或者使用 <function>sprintf</function> 打印其数字表示形式。
</simpara>
</listitem>
<listitem>
<simpara>
如果数据库层不支持绑定变量,则应使用特定于数据库的字符串转义函数(例如 <function>mysql_real_escape_string</function>、<function>sqlite_escape_string</function>
等)对传递给数据库的用户提供的非数字值进行转义。通用的函数如 <function>addslashes</function> 只在非常特定的环境中有用(例如在禁用了 <varname>NO_BACKSLASH_ESCAPES</varname>
的单字节字符集 MySQL),因此最好避免使用它们。
</simpara>
</listitem>
<listitem>
<simpara>
请勿以正当或非正当手段打印出任何特定于数据库的信息,特别是关于 schema 的信息。另请参阅<link
linkend="security.errors">错误报告</link>和<link linkend="ref.errorfunc">错误处理以及日志记录函数</link>。
</simpara>
</listitem>
</itemizedlist>
</para>
<simpara>
除此之外,如果数据库支持日志记录,还可以从脚本里或通过数据库自身记录查询语句。显然,日志记录无法阻止任何有害尝试,但它可以帮助追踪绕过了哪个应用程序。日志本身并没有用处,但通过其中包含的信息可以得到帮助。通常情况下,更详细的信息比较少的信息更好。
</simpara>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
indent-tabs-mode:nil
sgml-parent-document:nil
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
vim600: syn=xml fen fdm=syntax fdl=2 si
vim: et tw=78 syn=sgml
vi: ts=1 sw=1
-->