security/database.xml
7204e2dbb9b484c8b67bb5ad4a93fa1369c5b317
...
...
@@ -1,7 +1,6 @@
1
-
<?xml version="1.0" encoding="iso-8859-1"?>
2
-
<!-- $Revision: 1.15 $ -->
3
-
<!-- splitted from ./index.xml, last change in rev 1.66 -->
4
-
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook">
1
+
<?xml version="1.0" encoding="utf-8"?>
2
+
<!-- $Revision$ -->
3
+
<chapter xml:id="security.database" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
5
4
<title>Database Security</title>
6
5

7
6
<simpara>
...
...
@@ -18,9 +17,9 @@
18
17
linkend="security.database.sql-injection">tamper with an SQL query</link>.
19
18
</simpara>
20
19
<simpara>
21
-
As you can surmise, PHP cannot protect your database by itself. The
20
+
As you can surmise, <acronym>PHP</acronym> cannot protect your database by itself. The
22
21
following sections aim to be an introduction into the very basics of how to
23
-
access and manipulate databases within PHP scripts.
22
+
access and manipulate databases within <acronym>PHP</acronym> scripts.
24
23
</simpara>
25
24
<simpara>
26
25
Keep in mind this simple rule: defense in depth. The more places you
...
...
@@ -54,16 +53,6 @@
54
53
intruders gain access to your database using your applications credentials,
55
54
they can only effect as many changes as your application can.
56
55
</simpara>
57
-
<simpara>
58
-
You are encouraged not to implement all the business logic in the web
59
-
application (i.e. your script), instead do it in the database schema
60
-
using views, triggers or rules. If the system evolves, new ports will be
61
-
intended to open to the database, and you have to re-implement the logic
62
-
in each separate database client. Over and above, triggers can be used
63
-
to transparently and automatically handle fields, which often provides
64
-
insight when debugging problems with your application or tracing back
65
-
transactions.
66
-
</simpara>
67
56
</sect1>
68
57

69
58
<sect1 xml:id="security.database.connection">
...
...
@@ -78,100 +67,108 @@
78
67
<!--simpara>
79
68
If your database server has native SSL support, consider using <link
80
69
linkend="ref.openssl">OpenSSL functions</link> in communication between
81
-
PHP and database via SSL.
70
+
<acronym>PHP</acronym> and database via SSL.
82
71
</simpara-->
83
72
</sect1>
84
73

85
74
<sect1 xml:id="security.database.storage">
86
75
<title>Encrypted Storage Model</title>
87
76
<simpara>
88
-
SSL/SSH protects data travelling from the client to the server, SSL/SSH
89
-
does not protect the persistent data stored in a database. SSL is an
77
+
SSL/SSH protects data travelling from the client to the server: SSL/SSH
78
+
does not protect persistent data stored in a database. SSL is an
90
79
on-the-wire protocol.
91
80
</simpara>
92
81
<simpara>
93
82
Once an attacker gains access to your database directly (bypassing the
94
-
webserver), the stored sensitive data may be exposed or misused, unless
83
+
webserver), stored sensitive data may be exposed or misused, unless
95
84
the information is protected by the database itself. Encrypting the data
96
85
is a good way to mitigate this threat, but very few databases offer this
97
86
type of data encryption.
98
87
</simpara>
99
88
<simpara>
100
89
The easiest way to work around this problem is to first create your own
101
-
encryption package, and then use it from within your PHP scripts. PHP
90
+
encryption package, and then use it from within your <acronym>PHP</acronym> scripts. <acronym>PHP</acronym>
102
91
can assist you in this with several extensions, such as <link
103
-
linkend="ref.mcrypt">Mcrypt</link> and <link
104
-
linkend="ref.mhash">Mhash</link>, covering a wide variety of encryption
92
+
linkend="book.openssl">OpenSSL</link> and <link
93
+
linkend="book.sodium">Sodium</link>, covering a wide variety of encryption
105
94
algorithms. The script encrypts the data before inserting it into the database, and decrypts
106
95
it when retrieving. See the references for further examples of how
107
96
encryption works.
108
97
</simpara>
109
-
<simpara>
110
-
In case of truly hidden data, if its raw representation is not needed
111
-
(i.e. not be displayed), hashing may also be taken into consideration.
112
-
The well-known example for the hashing is storing the MD5 hash of a
113
-
password in a database, instead of the password itself. See also
114
-
<function>crypt</function> and <function>md5</function>.
115
-
</simpara>
116
-
<example>
117
-
<title>Using hashed password field</title>
118
-
<programlisting role="php">
98
+

99
+
<sect2 xml:id="security.database.storage.hashing">
100
+
<title>Hashing</title>
101
+
<simpara>
102
+
In the case of truly hidden data, if its raw representation is not needed
103
+
(i.e. will not be displayed), hashing should be taken into consideration.
104
+
The well-known example for hashing is storing the cryptographic hash of a
105
+
password in a database, instead of the password itself.
106
+
</simpara>
107
+
<simpara>
108
+
The <link linkend="ref.password">password</link> functions
109
+
provide a convenient way to hash sensitive data and work with these hashes.
110
+
</simpara>
111
+
<simpara>
112
+
<function>password_hash</function> is used to hash a given string using the
113
+
strongest algorithm currently available and <function>password_verify</function>
114
+
checks whether the given password matches the hash stored in database.
115
+
</simpara>
116
+
<example>
117
+
<title>Hashing password field</title>
118
+
<programlisting role="php">
119
119
<![CDATA[
120
120
<?php
121
121

122
122
// storing password hash
123
123
$query = sprintf("INSERT INTO users(name,pwd) VALUES('%s','%s');",
124
-
pg_escape_string($username), md5($password));
124
+
pg_escape_string($username),
125
+
password_hash($password, PASSWORD_DEFAULT));
125
126
$result = pg_query($connection, $query);
126
127

127
128
// querying if user submitted the right password
128
-
$query = sprintf("SELECT 1 FROM users WHERE name='%s' AND pwd='%s';",
129
-
pg_escape_string($username), md5($password));
130
-
$result = pg_query($connection, $query);
129
+
$query = sprintf("SELECT pwd FROM users WHERE name='%s';",
130
+
pg_escape_string($username));
131
+
$row = pg_fetch_assoc(pg_query($connection, $query));
131
132

132
-
if (pg_num_rows($result) > 0) {
133
-
echo 'Welcome, $username!';
133
+
if ($row && password_verify($password, $row['pwd'])) {
134
+
echo 'Welcome, ' . htmlspecialchars($username) . '!';
134
135
} else {
135
-
echo 'Authentication failed for $username.';
136
+
echo 'Authentication failed for ' . htmlspecialchars($username) . '.';
136
137
}
137
138

138
139
?>
139
140
]]>
140
-
</programlisting>
141
-
</example>
141
+
</programlisting>
142
+
</example>
143
+
</sect2>
142
144
</sect1>
143
145

144
146
<sect1 xml:id="security.database.sql-injection">
145
147
<title>SQL Injection</title>
146
148
<simpara>
147
-
Many web developers are unaware of how SQL queries can be tampered with,
148
-
and assume that an SQL query is a trusted command. It means that SQL
149
-
queries are able to circumvent access controls, thereby bypassing standard
150
-
authentication and authorization checks, and sometimes SQL queries even
151
-
may allow access to host operating system level commands.
152
-
</simpara>
153
-
<simpara>
154
-
Direct SQL Command Injection is a technique where an attacker creates or
155
-
alters existing SQL commands to expose hidden data, or to override valuable
156
-
ones, or even to execute dangerous system level commands on the database
157
-
host. This is accomplished by the application taking user input and
158
-
combining it with static parameters to build a SQL query. The following
159
-
examples are based on true stories, unfortunately.
149
+
SQL injection is a technique where an attacker exploits flaws in
150
+
application code responsible for building dynamic SQL queries.
151
+
The attacker can gain access to privileged sections of the application,
152
+
retrieve all information from the database, tamper with existing data,
153
+
or even execute dangerous system-level commands on the database
154
+
host. The vulnerability occurs when developers concatenate or
155
+
interpolate arbitrary input in their SQL statements.
160
156
</simpara>
161
157
<para>
162
-
Owing to the lack of input validation and connecting to the database on
163
-
behalf of a superuser or the one who can create users, the attacker
164
-
may create a superuser in your database.
165
158
<example>
166
159
<title>
167
160
Splitting the result set into pages ... and making superusers
168
161
(PostgreSQL)
169
162
</title>
163
+
<simpara>
164
+
In the following example, user input is directly interpolated into the
165
+
SQL query allowing the attacker to gain a superuser account in the database.
166
+
</simpara>
170
167
<programlisting role="php">
171
168
<![CDATA[
172
169
<?php
173
170

174
-
$offset = $argv[0]; // beware, no input validation!
171
+
$offset = $_GET['offset']; // beware, no input validation!
175
172
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
176
173
$result = pg_query($conn, $query);
177
174

...
...
@@ -180,10 +177,9 @@ $result = pg_query($conn, $query);
180
177
</programlisting>
181
178
</example>
182
179
Normal users click on the 'next', 'prev' links where the <varname>$offset</varname>
183
-
is encoded into the URL. The script expects that the incoming
184
-
<varname>$offset</varname> is a decimal number. However, what if someone tries to
185
-
break in by appending a <function>urlencode</function>'d form of the
186
-
following to the URL
180
+
is encoded into the <acronym>URL</acronym>. The script expects that the incoming
181
+
<varname>$offset</varname> is a number. However, what if someone tries to
182
+
break in by appending the following to the <acronym>URL</acronym>
187
183
<informalexample>
188
184
<programlisting role="sql">
189
185
<![CDATA[
...
...
@@ -195,13 +191,13 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
195
191
]]>
196
192
</programlisting>
197
193
</informalexample>
198
-
If it happened, then the script would present a superuser access to him.
194
+
If it happened, the script would present a superuser access to the attacker.
199
195
Note that <literal>0;</literal> is to supply a valid offset to the
200
196
original query and to terminate it.
201
197
</para>
202
198
<note>
203
199
<para>
204
-
It is common technique to force the SQL parser to ignore the rest of the
200
+
It is a common technique to force the SQL parser to ignore the rest of the
205
201
query written by the developer with <literal>--</literal> which is the
206
202
comment sign in SQL.
207
203
</para>
...
...
@@ -214,8 +210,8 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
214
210
LIMIT</literal> and <literal>OFFSET</literal> clauses in <literal>SELECT</literal>
215
211
statements. If your database supports the <literal>UNION</literal> construct,
216
212
the attacker may try to append an entire query to the original one to list
217
-
passwords from an arbitrary table. Using encrypted password fields is
218
-
strongly encouraged.
213
+
passwords from an arbitrary table. It is strongly recommended to store only
214
+
secure hashes of passwords instead of the passwords themselves.
219
215
<example>
220
216
<title>
221
217
Listing out articles ... and some passwords (any database server)
...
...
@@ -225,8 +221,7 @@ insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd)
225
221
<?php
226
222

227
223
$query = "SELECT id, name, inserted, size FROM products
228
-
WHERE size = '$size'
229
-
ORDER BY $order LIMIT $limit, $offset;";
224
+
WHERE size = '$size'";
230
225
$result = odbc_exec($conn, $query);
231
226

232
227
?>
...
...
@@ -244,18 +239,10 @@ union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from use
244
239
]]>
245
240
</programlisting>
246
241
</informalexample>
247
-
If this query (playing with the <literal>'</literal> and
248
-
<literal>--</literal>) were assigned to one of the variables used in
249
-
<varname>$query</varname>, the query beast awakened.
250
242
</para>
251
243
<para>
252
-
SQL UPDATE's are also susceptible to attack. These queries are
253
-
also threatened by chopping and appending an entirely new query to it. But
254
-
the attacker might fiddle with the <literal>SET</literal> clause. In this
255
-
case some schema information must be possessed to manipulate the query
256
-
successfully. This can be acquired by examining the form variable names, or
257
-
just simply brute forcing. There are not so many naming conventions for
258
-
fields storing passwords or usernames.
244
+
<literal>UPDATE</literal> and <literal>INSERT</literal> statements are also
245
+
susceptible to such attacks.
259
246
<example>
260
247
<title>
261
248
From resetting a password ... to gaining more privileges (any database server)
...
...
@@ -268,21 +255,21 @@ $query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
268
255
]]>
269
256
</programlisting>
270
257
</example>
271
-
But a malicious user sumbits the value
272
-
<literal>' or uid like'%admin%'; --</literal> to <varname>$uid</varname> to
258
+
If a malicious user submits the value
259
+
<literal>' or uid like'%admin%</literal> to <varname>$uid</varname> to
273
260
change the admin's password, or simply sets <varname>$pwd</varname> to
274
-
<literal>"hehehe', admin='yes', trusted=100 "</literal> (with a trailing
275
-
space) to gain more privileges. Then, the query will be twisted:
261
+
<literal>hehehe', trusted=100, admin='yes</literal> to gain more
262
+
privileges, then the query will be twisted:
276
263
<informalexample>
277
264
<programlisting role="php">
278
265
<![CDATA[
279
266
<?php
280
267

281
-
// $uid == ' or uid like'%admin%'; --
282
-
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";
268
+
// $uid: ' or uid like '%admin%
269
+
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
283
270

284
-
// $pwd == "hehehe', admin='yes', trusted=100 "
285
-
$query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
271
+
// $pwd: hehehe', trusted=100, admin='yes
272
+
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
286
273
...;";
287
274

288
275
?>
...
...
@@ -290,11 +277,25 @@ $query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
290
277
</programlisting>
291
278
</informalexample>
292
279
</para>
280
+
<simpara>
281
+
While it remains obvious that an attacker must possess at least some
282
+
knowledge of the database architecture to conduct a successful
283
+
attack, obtaining this information is often very simple. For example,
284
+
the code may be part of an open-source software and be publicly available.
285
+
This information may also be divulged
286
+
by closed-source code - even if it's encoded, obfuscated, or compiled -
287
+
and even by your own code through the display of error messages.
288
+
Other methods include the use of typical table and column names. For
289
+
example, a login form that uses a 'users' table with column names
290
+
'id', 'username', and 'password'.
291
+
</simpara>
293
292
<para>
294
-
A frightening example how operating system level commands can be accessed
295
-
on some database hosts.
296
293
<example>
297
-
<title>Attacking the database hosts operating system (MSSQL Server)</title>
294
+
<title>Attacking the database host operating system (MSSQL Server)</title>
295
+
<simpara>
296
+
A frightening example of how operating system-level commands can be
297
+
accessed on some database hosts.
298
+
</simpara>
298
299
<programlisting role="php">
299
300
<![CDATA[
300
301
<?php
...
...
@@ -315,8 +316,8 @@ $result = mssql_query($query);
315
316
<?php
316
317

317
318
$query = "SELECT * FROM products
318
-
WHERE id LIKE '%a%'
319
-
exec master..xp_cmdshell 'net user test testpass /ADD'--";
319
+
WHERE id LIKE '%a%'
320
+
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
320
321
$result = mssql_query($query);
321
322

322
323
?>
...
...
@@ -325,118 +326,140 @@ $result = mssql_query($query);
325
326
</informalexample>
326
327
MSSQL Server executes the SQL statements in the batch including a command
327
328
to add a new user to the local accounts database. If this application
328
-
were running as <literal>sa</literal> and the MSSQLSERVER service is
329
+
were running as <literal>sa</literal> and the MSSQLSERVER service was
329
330
running with sufficient privileges, the attacker would now have an
330
331
account with which to access this machine.
331
332
</para>
332
333
<note>
333
334
<para>
334
-
Some of the examples above is tied to a specific database server. This
335
+
Some examples above are tied to a specific database server, but it
335
336
does not mean that a similar attack is impossible against other products.
336
337
Your database server may be similarly vulnerable in another manner.
337
338
</para>
338
339
</note>
339
-

340
-
<sect2 xml:id="security.database.avoiding">
341
-
<title>Avoiding techniques</title>
342
-
<simpara>
343
-
You may plead that the attacker must possess a piece of information
344
-
about the database schema in most examples. You are right, but you
345
-
never know when and how it can be taken out, and if it happens,
346
-
your database may be exposed. If you are using an open source, or
347
-
publicly available database handling package, which may belong to a
348
-
content management system or forum, the intruders easily produce
349
-
a copy of a piece of your code. It may be also a security risk if it
350
-
is a poorly designed one.
351
-
</simpara>
352
-
<simpara>
353
-
These attacks are mainly based on exploiting the code not being written
354
-
with security in mind. Never trust any kind of input, especially that
355
-
which comes from the client side, even though it comes from a select box,
356
-
a hidden input field or a cookie. The first example shows that such a
357
-
blameless query can cause disasters.
358
-
</simpara>
359
-

360
-
<itemizedlist>
361
-
<listitem>
362
-
<simpara>
363
-
Never connect to the database as a superuser or as the database owner.
364
-
Use always customized users with very limited privileges.
365
-
</simpara>
366
-
</listitem>
367
-
<listitem>
340
+
<para>
341
+
<mediaobject>
342
+
<alt>A funny example of the issues regarding SQL injection</alt>
343
+
<imageobject>
344
+
<imagedata fileref="en/security/figures/xkcd-bobby-tables.png" format="PNG"/>
345
+
</imageobject>
346
+
<caption>
368
347
<simpara>
369
-
Check if the given input has the expected data type. PHP has
370
-
a wide range of input validating functions, from the simplest ones
371
-
found in <link linkend="ref.var">Variable Functions</link> and
372
-
in <link linkend="ref.ctype">Character Type Functions</link>
373
-
(e.g. <function>is_numeric</function>, <function>ctype_digit</function>
374
-
respectively) and onwards to the
375
-
<link linkend="ref.pcre">Perl compatible Regular Expressions</link>
376
-
support.
348
+
Image courtesy of <link xlink:href="&url.xkcd;327">xkcd</link>
377
349
</simpara>
378
-
</listitem>
379
-
<listitem>
380
-
<para>
381
-
If the application waits for numerical input, consider verifying data
382
-
with <function>is_numeric</function>, or silently change its type
383
-
using <function>settype</function>, or use its numeric representation
384
-
by <function>sprintf</function>.
385
-
<example>
386
-
<title>A more secure way to compose a query for paging</title>
387
-
<programlisting role="php">
350
+
</caption>
351
+
</mediaobject>
352
+
</para>
353
+

354
+
<sect2 xml:id="security.database.avoiding">
355
+
<title>Avoidance Techniques</title>
356
+
<para>
357
+
The recommended way to avoid SQL injection is by binding all data via
358
+
prepared statements. Using parameterized queries isn't enough to entirely
359
+
avoid SQL injection, but it is the easiest and safest way to provide input
360
+
to SQL statements. All dynamic data literals in <literal>WHERE</literal>,
361
+
<literal>SET</literal>, and <literal>VALUES</literal> clauses must be
362
+
replaced with placeholders. The actual data will be bound during the
363
+
execution and sent separately from the SQL command.
364
+
</para>
365
+
<para>
366
+
Parameter binding can only be used for data. Other dynamic parts of the
367
+
SQL query must be filtered against a known list of allowed values.
368
+
</para>
369
+
<para>
370
+
<example>
371
+
<title>Avoiding SQL injection by using PDO prepared statements</title>
372
+
<programlisting role="php">
388
373
<![CDATA[
389
374
<?php
390
375

391
-
settype($offset, 'integer');
392
-
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
393
-

394
-
// please note %d in the format string, using %s would be meaningless
395
-
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
396
-
$offset);
376
+
// The dynamic SQL part is validated against expected values
377
+
$sortingOrder = $_GET['sortingOrder'] === 'DESC' ? 'DESC' : 'ASC';
378
+
$productId = $_GET['productId'];
379
+
// The SQL is prepared with a placeholder
380
+
$stmt = $pdo->prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}");
381
+
// The value is provided with LIKE wildcards
382
+
$stmt->execute(["%{$productId}%"]);
397
383

398
384
?>
399
385
]]>
400
-
</programlisting>
401
-
</example>
402
-
</para>
403
-
</listitem>
404
-
<listitem>
405
-
<simpara>
406
-
Quote each non numeric user supplied value that is passed to the
407
-
database with the database-specific string escape function (e.g.
408
-
<function>mysql_real_escape_string</function>,
409
-
<function>sqlite_escape_string</function>, etc.). If a database-specific
410
-
string escape mechanism is not available, the
411
-
<function>addslashes</function> and <function>str_replace</function>
412
-
functions may be useful (depending on database type).
413
-
See <link linkend="security.database.storage">the first example</link>.
414
-
As the example shows, adding quotes to the static part of the query
415
-
is not enough, making this query easily crackable.
416
-
</simpara>
417
-
</listitem>
418
-
<listitem>
419
-
<simpara>
420
-
Do not print out any database specific information, especially
421
-
about the schema, by fair means or foul. See also <link
422
-
linkend="security.errors">Error Reporting</link> and <link
423
-
linkend="ref.errorfunc">Error Handling and Logging Functions</link>.
424
-
</simpara>
425
-
</listitem>
426
-
<listitem>
427
-
<simpara>
428
-
You may use stored procedures and previously defined cursors to abstract
429
-
data access so that users do not directly access tables or views, but
430
-
this solution has another impacts.
431
-
</simpara>
432
-
</listitem>
433
-
</itemizedlist>
386
+
</programlisting>
387
+
</example>
388
+
</para>
389
+

390
+
<simpara>
391
+
Prepared statements are provided
392
+
<link linkend="pdo.prepared-statements">by PDO</link>,
393
+
<link linkend="mysqli.quickstart.prepared-statements">by MySQLi</link>,
394
+
and by other database libraries.
395
+
</simpara>
396
+

397
+
<simpara>
398
+
SQL injection attacks are mainly based on exploiting the code not being written
399
+
with security in mind. Never trust any input, especially
400
+
from the client side, even though it comes from a select box,
401
+
a hidden input field, or a cookie. The first example shows that such a
402
+
simple query can cause disasters.
403
+
</simpara>
404
+

405
+
<para>
406
+
A defense-in-depth strategy involves several good coding practices:
407
+
<itemizedlist>
408
+
<listitem>
409
+
<simpara>
410
+
Never connect to the database as a superuser or as the database owner.
411
+
Use always customized users with minimal privileges.
412
+
</simpara>
413
+
</listitem>
414
+
<listitem>
415
+
<simpara>
416
+
Check if the given input has the expected data type. <acronym>PHP</acronym> has
417
+
a wide range of input validating functions, from the simplest ones
418
+
found in <link linkend="ref.var">Variable Functions</link> and
419
+
in <link linkend="ref.ctype">Character Type Functions</link>
420
+
(e.g. <function>is_numeric</function>, <function>ctype_digit</function>
421
+
respectively) and onwards to the
422
+
<link linkend="ref.pcre">Perl Compatible Regular Expressions</link>
423
+
support.
424
+
</simpara>
425
+
</listitem>
426
+
<listitem>
427
+
<simpara>
428
+
If the application expects numerical input, consider verifying data
429
+
with <function>ctype_digit</function>, silently change its type
430
+
using <function>settype</function>, or use its numeric representation
431
+
by <function>sprintf</function>.
432
+
</simpara>
433
+
</listitem>
434
+
<listitem>
435
+
<simpara>
436
+
If the database layer doesn't support binding variables then
437
+
quote each non-numeric user-supplied value that is passed to the
438
+
database with the database-specific string escape function (e.g.
439
+
<function>mysql_real_escape_string</function>,
440
+
<function>sqlite_escape_string</function>, etc.).
441
+
Generic functions like <function>addslashes</function> are useful only
442
+
in a very specific environment (e.g. MySQL in a single-byte character
443
+
set with disabled <varname>NO_BACKSLASH_ESCAPES</varname>), so it is
444
+
better to avoid them.
445
+
</simpara>
446
+
</listitem>
447
+
<listitem>
448
+
<simpara>
449
+
Do not print out any database-specific information, especially
450
+
about the schema, by fair means or foul. See also <link
451
+
linkend="security.errors">Error Reporting</link> and <link
452
+
linkend="ref.errorfunc">Error Handling and Logging Functions</link>.
453
+
</simpara>
454
+
</listitem>
455
+
</itemizedlist>
456
+
</para>
434
457

435
458
<simpara>
436
459
Besides these, you benefit from logging queries either within your script
437
460
or by the database itself, if it supports logging. Obviously, the logging is unable
438
461
to prevent any harmful attempt, but it can be helpful to trace back which
439
-
application has been circumvented. The log is not useful by itself, but
462
+
application has been circumvented. The log is not useful by itself but
440
463
through the information it contains. More detail is generally better than less.
441
464
</simpara>
442
465
</sect2>
...
...
@@ -454,7 +477,7 @@ sgml-indent-step:1
454
477
sgml-indent-data:t
455
478
indent-tabs-mode:nil
456
479
sgml-parent-document:nil
457
-
sgml-default-dtd-file:"../../manual.ced"
480
+
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
458
481
sgml-exposed-tags:nil
459
482
sgml-local-catalogs:nil
460
483
sgml-local-ecat-files:nil
461
484