MySQL/API
请记住,互联网是由那些不希望我们有任何秘密的人创造的。还要记住,很多人被付钱来了解我们的秘密,并将它们注册在某个地方。
偏执是一种智慧形式。
有时,连接参数(包括用户名和密码)存储在纯文本文件中,例如 .ini 文件。这是不安全的:如果用户猜到它的名称,他就可以读取它。如果它位于 Web 服务器的 WWW 目录之外,它更安全,但将其作为常量存储在程序文件中是一个更好的做法。
用户总是可能获得您的 FTP 密码或其他密码。因此,用于连接到 MySQL 的用户名和密码应与其他用户名/密码不同。
MySQL 密码必须安全。您无需记住它们。它们应包含小写字母、大写字母、数字和符号(如 '_');它们不应包含现有单词或您的出生日期;它们绝不应通过电子邮件发送(如果发送,必须有一些方法可以修改它们);它们不应存储在绝对没有必要存储它们的地方。
在理想世界中,您将知道 $_POST 中包含的值是可以插入 SQL 语句中的值。但在理想世界中,没有贫困或专有软件,因此情况并非如此。这些值可能包含称为“SQL 注入”的攻击。当您期望像“'42'”这样的值时,您可能会发现像“'42' OR 1”这样的值。因此,当您尝试创建这样的语句时
DELETE FROM `articles` WHERE `id`=42
您可能会创建这样的语句:
DELETE FROM `articles` WHERE `id`=42 OR 1
这会删除所有记录。
在某些情况下,您尝试执行这样的查询
SELECT * FROM `my_nice_table` WHERE title='bla bla'
用户可能会将其更改为
SELECT * FROM `my_nice_table` WHERE title='bla bla'; TRUNCATE TABLE `my_nice_table`
这些只是示例。如果所有记录都从您的表中消失,很容易意识到。如果表已正确备份,您可以重新填充它们。但也有更糟糕的情况。如果用户学会了如何操纵您的数据库,他可以为自己创建一个管理帐户,或者对您网站的内容进行您永远不会看到的修改,甚至可以注册他未支付的款项。
简单来说,必须表示值的输入,如果包含更多内容,则不应接受。
- 字符串值
它们用“引号”括起来。它们中出现的每个引号都应转换为“''”或“\'”。PHP 建议使用 `mysql_real_escape_string` 来替换这些特殊字符。
- 数字(整数、浮点数)
它们必须是数字输入。如果它们包含“OR”或空格等内容,则不是数字。
- 日期
将它们用“引号”括起来,并像对待字符串一样管理它们。
- NULL / UNKNOWN / TRUE /FALSE
这些值绝不应由用户输入,而应以编程方式创建。
- SQL 名称
在某些情况下,SQL 名称可能包含在用户输入中。一个常见的例子是在 ORDER BY 子句中使用的列名,它可能来自 $_GET。将它们用“反引号”括起来,并将每个“`”替换为“``”。当然,一般来说,如果 SQL 名称仅在 ORDER BY 子句中使用,这是一种非常糟糕的做法。
- 注释
用户输入绝不应插入 SQL 注释中。
当密码存储在数据库中时,它们通常是加密的。加密应由脚本完成,而不是由 MySQL 完成。如果它是通过 SQL 完成的,密码将通过语句以纯文本形式写入。这意味着它们可以通过以下方式查看
- 可能,一些系统日志,如果与数据库的通信是通过网络进行的并且没有加密
- MySQL 日志
- SHOW PROCESSLIST
因此,人们绝不应该发送这样的查询
SELECT 1 FROM `users` WHERE `password`=MD5('abraxas')
但是,在 PHP 中,您应该编写
$sql = "SELECT 1 FROM `users` WHERE `password`=MD5('".md5('abraxas')."')";
您绝不应使用不安全的加密函数,如 PASSWORD()。此外,您也不应使用双向加密。只有 SHA256 等密码散列是安全的,并且不要使用 MD5 等旧的散列算法。
密码,即使它们被安全地加密,也不应通过 SELECT 检索。这是不安全的,单向加密不需要这样做。
如果您的所有数据库内容都是公开的,则没有理由使用加密进行通信。但通常情况下并非如此。即使这样,也可能有一组有限的人员被授权向网站提交新内容,这将需要使用密码。
因此,通常情况下,使用 SSL 加密是一个好主意。请参阅驱动程序的文档,了解如何执行此操作(这始终是一个简单的连接选项)。
SSL 不仅会加密包含用户密码的网络流量,还可以使用证书验证网站是否为正确网站。一种可能的攻击是创建了一个看起来像受害者网站的网站,试图让您提交用户名和密码。
通过使用持久连接,我们保持与服务器的连接打开,以便可以执行多个查询,而无需每次运行脚本时关闭和重新打开连接的开销。
请注意,这并不总是最佳优化。试想一下,如果每个托管网站都只使用持久连接,服务器的 RAM 应该存储多少持久连接:一次会有太多连接。
持久连接可以通过许多语言使用。
当您执行查询时,您会获得一个记录集并将它放入一个变量中。当您不再需要它时将其保留在内存中是浪费 RAM。这就是为什么通常您应该尽快释放内存。如果只有在脚本结束前几行才有可能,那么这样做就没有意义。但在某些情况下,这样做是好的。
许多 API 支持两种提取行的方法:您可以将它们放入普通数组、对象或关联数组中。将行放入对象是最慢的方式,而将它们放入普通数组是最快的方式。如果您每行检索一个值,那么将其放入数组可能是一个好主意。
通常,API 支持一些创建 SQL 语句并将其发送到 MySQL 服务器的方法。您可以通过手动创建语句来获得相同的效果,但它是一种较慢的方式。API 的方法通常更优化。
- 一些脚本使用两个查询来提取透视表。
客户端/服务器通信通常是瓶颈,因此您应该尝试只使用一个 JOIN 而不是多个 JOIN。
- 如果需要使用多个查询,则应尽可能只使用一个连接。
- 只检索您真正需要的字段。
- 尽量不要在 SQL 命令中包含太多无意义的字符(空格、制表符、注释...)。
当您从现有表创建新表时,您应该使用 CREATE ... SELECT。当您想从查询填充现有表时,您应该使用 INSERT ... SELECT 或 REPLACE ... SELECT。这样,您将告诉服务器通过仅发送一个 SQL 语句来执行所有需要的操作。
许多脚本不检查 INSERT 是否成功。如果是这种情况,您应该使用 INSERT DELAYED 而不是 INSERT。这样,客户端在继续操作之前不会等待来自服务器的确认。
如果您运行一个 DELETE 然后是一个 INSERT,您需要将两个 SQL 命令传送到服务器。也许您可能想使用 REPLACE 而不是 DELETE 和 INSERT。可能的话,使用 REPLACE DELAYED。
有时,会话数据存储在数据库中。这需要每次用户加载页面时至少进行一次 UPDATE 和一次 SELECT。通过将会话数据存储在 cookie 中可以避免这种情况。
浏览器允许用户不接受 cookie,但如果他们不接受 cookie,他们将无法访问许多重要的现代网站。
唯一不能安全地存储在 cookie 中的数据是密码。您可以为 cookie 设置一个简短的有效期,这样用户的隐私就不会受到您的 cookie 的严重损害。或者,您可以执行以下操作
- 当用户成功登录您的网站时,使用 CURRENT_TIMESTAMP() 和随机 ID 创建一个记录;
- 使用该 ID 设置一个 cookie;
- 当用户尝试执行某些操作时,检查他是否已登录
SELECT FROM `access` WHERE `id`=id_from_cookie AND `tstamp`>=CURRENT_TIMESTAMP() - login_lifetime
- 更新 tstamp
当用户浏览文章或其他动态内容(即存储在数据库中的内容)时,需要生成一个 HTML 文档。通常,页面没有可变内容,只有在插入一次后很少(或从不)更新的内容。文章或链接列表就是一个很好的例子。
因此,创建一个程序,当文章插入到数据库时,生成一个静态 HTML 页面,可能是一个好主意。如果文章更新,页面可以被删除并重新生成。这可以节省大量 SQL 语句和 DBMS 的工作。
当然,这需要您可能没有的某些权限。如果您使用的是托管服务,您可能需要与技术支持团队讨论此事。
PHP 具有以下适用于 MySQL 的官方驱动程序
- mysql - 较旧,因此仍被许多 Web 应用程序使用;它是一个过程式的 PHP 模块
- mysqli - 更快;可以用作一组类或普通过程式库
- PDO(PHP 数据对象) - 使用 PDO,一个用于与数据库交互的抽象层,它具有用于 MySQL 和 ODBC 的驱动程序。
- PDO_MYSQL 支持一些高级 MySQL 功能,并在不存在时模拟这些功能。
上述驱动程序中的函数是扩展调用 C API 中的方法。它们可以使用 MySQL 客户端库或 mysqlnd,一个适用于 PHP 的原生驱动程序。
有时,启用 mysql 和 mysqli 都会导致一些问题;因此,如果您只使用其中一个,您应该禁用另一个。
此外,PHP 还具有一个可以与 MySQL 一起使用的 ODBC 扩展。
PEAR 是一组重要的 PHP 类,支持 MySQL。
PHP 具有一个名为 register_globals 的环境变量。从 PHP 4.2 开始,默认情况下它被设置为 false,您不应该设置它。在 PHP 5.3 中,此变量也被弃用,在 PHP 6 中已被删除。
然而,如果您的 PHP 版本支持 `register_globals`,您可以通过调用 `ini_get()` 函数来验证它是否设置为 `true`。但是,如果它为 `true`,您就无法使用 `ini_set()` 修改它。有两种方法可以将其关闭:
- 编辑 `php.ini` 文件
(如果您使用的是托管服务,则无法进行此操作)
- 在 `.htaccess` 文件中添加一行代码
php_flag register_globals off
(有时在托管服务中可能有效)
原因是,如果 `register_globals` 为 `true`,用户可以通过以下方式调用变量,从而向您的脚本中任意添加变量:
your_script.php?new_variable=new_value
您永远不应使用 `$_REQUEST` 超级全局数组。它可以用来从以下地方检索变量:
- $_ENV
- $_GET
- $_POST
- $_COOKIE
- $_SERVER
这是 PHP 遵循的顺序(可能由 `variables_order` 环境变量修改)。这意味着,如果您的脚本设置了一个名为 "userid" 的服务器变量,而您尝试通过 `$_REQUEST` 读取它,用户可以通过在查询字符串中添加一个变量来阻止您这样做。
此外,您永远不应该盲目信任 HTTP 变量的有效性。