【问题标题】:How to secure database passwords in PHP?如何在 PHP 中保护数据库密码?
【发布时间】:2010-09-11 00:15:26
【问题描述】:

当 PHP 应用程序建立数据库连接时,它当然通常需要传递登录名和密码。如果我为我的应用程序使用单一的、最低权限的登录名,那么 PHP 需要在某处知道该登录名和密码。保护该密码的最佳方法是什么?似乎只用 PHP 代码编写它不是一个好主意。

【问题讨论】:

  • 为了完全安全,您需要设置 ssl 连接,否则您网络上的任何人仍然可以嗅探您输入的密码。
  • 你是指连接字符串中使用的用户密码还是数据库密码?
  • 连接字符串中使用的数据库密码。谢谢!

标签: php database security


【解决方案1】:

实际上,最佳做法是将数据库凭据存储在环境变量中,因为:

  • 这些凭据取决于环境,这意味着您在 dev/prod 中不会拥有相同的凭据。将它们存储在所有环境的同一个文件中是错误的。
  • 凭据与业务逻辑无关,这意味着登录名和密码与您的代码无关。
  • 您可以在不创建任何业务代码类文件的情况下设置环境变量,这意味着您永远不会在 Git 中将凭据文件添加到提交中。
  • 环境变量是超全局变量:您可以在代码中的任何地方使用它们,而无需包含任何文件。

如何使用它们?

  • 使用 $_ENV 数组:
    • 设置:$_ENV['MYVAR'] = $myvar
    • Gettingecho $_ENV["MYVAR"]
  • 使用 php 函数:
  • In vhosts files and .htaccess 但不建议这样做,因为它在另一个文件中,并且无法通过这种方式解决问题。

您可以轻松地删除一个包含所有环境变量的文件,例如 envvars.php,然后执行它(php envvars.php)并删除它。这有点老派,但它仍然有效,并且您在服务器中没有任何包含您的凭据的文件,您的代码中也没有凭据。由于有点费力,框架做得更好。

使用 Symfony 的示例(好吧,不仅仅是 PHP) Symfony 等现代框架建议使用环境变量,并将它们存储在 .env 未提交的文件中或直接存储在命令行中,这意味着您可以这样做:

文档:

【讨论】:

    【解决方案2】:

    如果可以在存储凭据的同一文件中创建数据库连接。在连接语句中内联凭据。

    mysql_connect("localhost", "me", "mypass");
    

    否则最好在connect语句之后取消设置凭据,因为不在内存中的凭据不能是read from memory ;)

    include("/outside-webroot/db_settings.php");  
    mysql_connect("localhost", $db_user, $db_pass);  
    unset ($db_user, $db_pass);  
    

    【讨论】:

    • 如果有人可以访问内存,那么无论如何你都完蛋了。这是毫无意义的虚假安全。在 webroot 之外(或至少受 .htaccess 保护,如果您在 webroot 之上没有访问权限)是唯一安全的选择。
    • @uliwitness - 这就像说,仅仅因为有人可以用乙炔火炬打开您的网络运营中心的锁,就意味着门也是假安全。将敏感信息限制在尽可能严格的范围内总是有意义的。
    • echo $db_user 或打印 $db_pass 怎么样?即使是同一团队的开发人员也不应该能够弄清楚生产凭证。该代码不应包含有关登录信息的任何可打印内容。
    • @LukeA.Leber 有了适当的安全措施,锁应该不会再增加安全性。锁只是为了降低设备被盗的可能性,但以防设备被盗,设备不应包含敏感和/或未加密的数据。
    【解决方案3】:

    之前我们将 DB 用户/密码存储在配置文件中,但后来进入了偏执模式——采用了深度防御的策略。

    如果您的应用程序遭到入侵,用户将拥有对您的配置文件的读取权限,因此破解者有可能读取此信息。配置文件也可能会陷入版本控制,或在服务器周围复制。

    我们已切换到在 Apache VirtualHost 中设置的环境变量中存储用户/密码。此配置只能由 root 读取 - 希望您的 Apache 用户没有以 root 身份运行。

    与此不同的是,现在密码位于全局 PHP 变量中。

    为降低这种风险,我们采取了以下预防措施:

    • 密码是加密的。 我们扩展了 PDO 类以包含用于解密密码的逻辑。如果有人阅读了我们建立连接的代码,那么很明显,连接是使用加密密码而不是密码本身建立的。
    • 加密密码从全局变量转移到私有变量中应用程序立即执行此操作以减少该值在全局空间中可用的窗口。
    • phpinfo() 已禁用。 PHPInfo 是一个简单的目标,可以轻松了解所有内容,包括环境变量。

    【讨论】:

    • “此配置只能由 root 读取” - 尽管设置的环境变量可能每个人都可以读取?
    • @MrWhite,环境变量只会为 Apache 运行的用户设置。所以它绝对不是每个人都可读的。
    【解决方案4】:

    我们是这样解决的:

    1. 在服务器上使用 memcache,并打开来自其他密码服务器的连接。
    2. 将密码(甚至所有加密的 password.php 文件)和解密密钥保存到 memcache。
    3. 网站调用保存密码文件密码短语的 memcache 密钥并在内存中解密所有密码。
    4. 密码服务器每 5 分钟发送一个新的加密密码文件。
    5. 如果您在项目中使用加密的 password.php,您需要进行审核,以检查该文件是否被外部触摸或查看过。发生这种情况时,您可以自动清理内存,以及关闭服务器以供访问。

    【讨论】:

      【解决方案5】:

      此解决方案是通用的,因为它对开源和闭源应用程序都很有用。

      1. 为您的应用程序创建一个操作系统用户。见http://en.wikipedia.org/wiki/Principle_of_least_privilege
      2. 使用密码为该用户创建(非会话)操作系统环境变量
      3. 以该用户身份运行应用程序

      优点:

      1. 您不会偶然将密码检查到源代码管理中,因为您不能
      2. 您不会不小心搞砸文件权限。好吧,你可以,但这不会影响到这一点。
      3. 只能由 root 或该用户读取。无论如何,Root 都可以读取您的所有文件和加密密钥。
      4. 如果您使用加密,您如何安全地存储密钥?
      5. Works x 平台
      6. 确保不要将 envvar 传递给不受信任的子进程

      这个方法是 Heroku 提出的,非常成功。

      【讨论】:

        【解决方案6】:

        只是将其放入某个配置文件中是通常的做法。只要确保你:

        1. 禁止从网络外的任何服务器访问数据库,
        2. 注意不要意外向用户显示密码(在错误消息中,或通过 PHP 文件意外作为 HTML 提供,等等。)

        【讨论】:

          【解决方案7】:

          最安全的方法是完全不在您的 PHP 代码中指定信息。

          如果您使用的是 Apache,这意味着在您的 httpd.conf 或虚拟主机文件文件中设置连接详细信息。如果你这样做,你可以不带参数调用 mysql_connect(),这意味着 PHP 永远不会输出你的信息。

          这是您在这些文件中指定这些值的方式:

          php_value mysql.default.user      myusername
          php_value mysql.default.password  mypassword
          php_value mysql.default.host      server
          

          然后你像这样打开你的mysql连接:

          <?php
          $db = mysqli_connect();
          

          或者像这样:

          <?php
          $db = mysqli_connect(ini_get("mysql.default.user"),
                               ini_get("mysql.default.password"),
                               ini_get("mysql.default.host"));
          

          【讨论】:

          • 请检查ini_get('default values') php.net/manual/en/class.mysqli.php的正确值
          • 是的,但是任何用户(或滥用编写糟糕的 php 脚本的黑客)都可以通过 ini_get() 读取密码。
          • @Marki555 but any user (or a hacker abusing badly written php script) can read the password via ini_get()你怎么处理这个?
          • Marki555 的意思是,可以运行 PHP 代码的攻击者也可以调用 PHP 函数,这显然是对的,而且是不可能的。我还想补充一点,我自己不再遵循我在这个答案中给出的建议,而是使用环境变量。这个概念是相似的:不要将您的凭据存储在代码中,而是以某种方式注入它们。使用ini_get()getenv() 并不重要。
          • @DeepBlue 如果你可以注入 ini_get() 你也可以注入 file_get_contents(anypath) 。只要 php 有办法获取密码,任何恶意代码都可以。
          【解决方案8】:

          一些人将此误解为关于如何在数据库中存储密码的问题。那是错的。它是关于如何存储让您进入数据库的密码。

          通常的解决方案是将密码从源代码中移到配置文件中。然后将管理和保护该配置文件的工作留给您的系统管理员。这样开发人员就不需要知道任何有关生产密码的信息,并且在您的源代码管理中没有密码记录。

          【讨论】:

          • 谢谢。如果我理解正确,则 php 文件将包含配置文件,允许它使用密码。例如我创建了一个名为“app1_db_cfg.php”的文件,其中存储了登录名、密码和数据库名称。然后我的 application.php 页面包含“app1_db_cfg.php”,我在做生意!
          • 我同意配置需要得到适当的保护。然而,知道如何做到这一点是系统管理员的事情,而不是开发人员的事情。在这种情况下,我不同意强加密的价值。如果你不能保护你的配置文件,是什么让你认为你可以保护你的密钥?
          • 我更喜欢使用只允许从 Web 服务器访问数据库的数据库帐户。然后我不费心加密配置,我只是将它存储在 web 根目录之外。
          • 我使用 apache 环境变量来设置路径,这样即使文件的路径在源代码中也是未知的。这也很好地允许根据服务器上的 Apache 设置为开发和生产设置不同的密码
          • 请记住,即使存储在 Web 可访问目录之外的文件也必须由使用它们的脚本读取。如果有人包含该文件,然后从文件中转储数据,他们将看到密码。
          【解决方案9】:

          如果您托管在其他人的服务器上并且在您的 webroot 之外没有访问权限,您始终可以将您的密码和/或数据库连接放在一个文件中,然后使用 .htaccess 锁定该文件:

          <files mypasswdfile>
          order allow,deny
          deny from all
          </files>
          

          【讨论】:

          • 谢谢,这正是我想要的。
          • 当然可以,但是如果有人拥有 shell 访问权限,那么无论如何,您的整个帐户都已被盗用。
          • 这是一种不好的做法,因为您可能会不小心将凭据提交到存储库。
          • @Ankit:如果不友好的人可以上传文件到服务器并执行它,那么服务器没有正确配置。
          • @Porlune:开发人员应该让他们的版本控制系统忽略密码文件,即使用 .gitignore。但是,是的,应该小心包含敏感数据的文件。
          【解决方案10】:

          最好的办法是根本不存储密码!
          例如,如果您在 Windows 系统上并连接到 SQL Server,则可以使用集成身份验证以使用当前进程的身份连接到数据库而无需密码。

          如果您确实需要使用密码进行连接,请先加密,使用强加密(例如使用 AES-256,然后保护加密密钥,或使用非对称加密并让操作系统保护证书),然后使用 strong ACLs 将其存储在配置文件(Web 目录之外)中。

          【讨论】:

          • 没有意义再次加密密码。可以获取未加密密码的人也可以获取解密密码所需的任何密码。但是,使用 ACL 和 .htaccess 是个好主意。
          • @uliwitness 我想你可能误解了——“加密再次”是什么意思?这只是一种加密。而且您不想使用密码短语(供人类使用)来加密它,而是使用强大的密钥管理,例如受操作系统保护,以这种方式简单地访问文件系统不会授予对密钥的访问权限。
          • 加密并不神奇——您可以将密码存储在那里,而不是使用 ACL 保护 AES 密钥。访问 AES 密钥或解密密码没有区别,在这种情况下加密只是蛇油。
          • @MarioVilas 什么?如果密码是加密的,用操作系统保护的加密密钥,怎么没有区别?加密不是魔术——它只是将所有机密压缩到较小的加密密钥中。几乎没有蛇油,在这种情况下,它只是 移动所有保密到操作系统中。
          • @AviD 为什么操作系统可以保护密钥而不是数据本身?答:它可以保护两者,所以加密并没有真正的帮助。如果只存储数据并派生加密密钥(例如,从必须由用户键入的密码),情况会有所不同。
          【解决方案11】:

          对于极其安全的系统,我们在配置文件中加密数据库密码(该文件本身由系统管理员保护)。在应用程序/服务器启动时,应用程序会提示系统管理员输入解密密钥。然后从配置文件中读取数据库密码,解密并存储在内存中以备将来使用。仍然不是 100% 安全,因为它存储在已解密的内存中,但在某些时候你必须称它为“足够安全”!

          【讨论】:

          • @RaduMurzea 这太荒谬了。你什么时候听说过系统管理员死了?它们就像麦当劳一样,只是突然出现/消失!
          • @Radu Murzea 只要有 2 个或更多管理员,那么您就有了像 RAID 阵列一样的奇偶校验。一次发生多个驱动器故障的可能性要低得多。
          • 服务器重启后呢?唤醒管理员以让他们在..etc.etc 中输入密码所需的时间呢?大声笑
          • iam 100% 同意,这是 oracle weblogic 使用 boot.properties 完成的
          • 不确定“存储在内存中”是什么意思。 PHP Web 应用程序在内存中存储任何内容的时间通常不会超过响应单个请求以查看页面所需的时间。
          【解决方案12】:

          另一个技巧是使用 PHP 单独的配置文件,如下所示:

          <?php exit() ?>
          
          [...]
          
          Plain text data including password
          

          这不会妨碍您正确设置访问规则。但如果您的网站被黑客入侵,“require”或“include”只会在第一行退出脚本,因此更难获取数据。

          但是,永远不要将配置文件放在可以通过网络访问的目录中。您应该有一个“Web”文件夹,其中包含您的控制器代码、css、图片和 js。就这样。其他任何内容都放在脱机文件夹中。

          【讨论】:

          • 但是 php 脚本如何读取存储在文件中的凭据?
          • 你使用 fopen(),就像一个普通的文本文件一样。
          • @e-satis 好的,它会阻止黑客做require/include,但是如何防止做fopen
          • "这并不妨碍您正确设置访问规则"
          • @e-satis,这很聪明。奇怪为什么没有人想到它。 然而,仍然容易受到编辑器复制问题的影响。 feross.org/cmsploit
          【解决方案13】:

          如果您使用的是 PostgreSQL,那么它会自动在 ~/.pgpass 中查找密码。请参阅the manual 了解更多信息。

          【讨论】:

            【解决方案14】:

            您的选择是有限的,因为您说您需要密码才能访问数据库。一种通用方法是将用户名和密码存储在单独的配置文件中,而不是主脚本中。然后确保将其存储在主网络树之外。那是如果存在网络配置问题,导致您的 php 文件仅显示为文本而不是被执行,那么您还没有暴露密码。

            除此之外,您对所用帐户的访问权限极少。添加到那个

            • 请勿将用户名/密码组合用于其他任何目的
            • 将数据库服务器配置为仅接受来自该用户的 Web 主机的连接(如果数据库位于同一台计算机上,则 localhost 会更好)这样即使凭据被公开,除非他们有其他凭据,否则它们对任何人都没有用访问机器。
            • 混淆密码(即使是 ROT13 也会这样做)如果某些人确实可以访问该文件,它不会进行太多防御,但至少可以防止随意查看它。

            彼得

            【讨论】:

              【解决方案15】:

              如果您谈论的是数据库密码,而不是来自浏览器的密码,标准做法似乎是将数据库密码放在服务器上的 PHP 配置文件中。

              您只需要确保包含密码的 php 文件对其具有适当的权限。 IE。它应该只能由 Web 服务器和您的用户帐户读取。

              【讨论】:

              • 不幸的是,phpinfo() 可以读取 PHP 配置文件,如果有人碰巧留下一些测试脚本,幸运的攻击者将能够读取密码。最好将连接密码保留在 Web 服务器根目录之外的文件中。那么访问它的唯一方法是使用 shell 或执行任意代码,但在这种情况下,无论如何都会失去所有安全性。
              • @MarioVilas “phpinfo() 可以读取 PHP 配置文件” - 我认为答案是指包含配置信息的任意 PHP 脚本,而不是 php.ini (config) 文件(我认为这就是您所指的)。这不会“被 phpinfo() 读取”。
              • @MrWhite 你当然是完全正确的。我误解了答案意味着将数据库凭据存储在 php.ini 本身中。
              【解决方案16】:

              将数据库密码放在一个文件中,使其对提供文件的用户只读。

              除非你有一些方法只允许 php 服务器进程访问数据库,否则你能做的几乎就是所有这些。

              【讨论】:

                【解决方案17】:

                将它们存储在 Web 根目录之外的文件中。

                【讨论】:

                • 而且,正如其他地方所提到的,在源代码控制之外。
                • 我们可以包含它吗?例如然后我们可以在 PHP 中做 include('../otherDirectory/configfile.conf') 吗?
                • 你们都建议将凭据存储在 wwwroot 之外。好的,我了解安全背景。但是它应该如何存储在版本控制中(示例配置)?通常 wwwroot 是 git repo 的根目录,所以如果外面有任何东西 - 它将在 VC 之外。想象一下新开发人员试图建立一个本地实例进行开发——他怎么会知道“拿这个文件,把它复制到外面然后填写”这样的魔法?
                • @TheGodfather 这个想法是新开发人员应该拥有自己的开发环境凭据。虽然在代码中包含说明或 cmets 的自述文件是一个很好的做法,指示您应该如何配置它(但不是实际数据)。
                猜你喜欢
                • 1970-01-01
                • 2014-07-22
                • 2019-07-14
                • 1970-01-01
                • 2012-08-11
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多