PHP は他の言語に比べて Web サイト構築に大きなメリットがあり、初心者でも簡単に Web サイトを構築できます。しかし、多くの PHP チュートリアルにはセキュリティの知識が含まれていないため、この利点は容易に悪影響をもたらす可能性もあります。
この投稿は複数のパートに分かれており、各パートではさまざまなセキュリティの脅威と対策を取り上げます。ただし、これらのポイントを実行すれば、Web サイトに関する問題が確実に回避されるという意味ではありません。 Web サイトのセキュリティを向上させたい場合は、書籍や記事を読んで Web サイトのセキュリティを向上させる方法を引き続き学習する必要があります。
デモ目的のため、コードは完全ではない可能性があります。日々の開発プロセスでは、フレームワークやさまざまなライブラリに多くのコードが含まれています。バックエンド開発者は、基本的な CURD に精通しているだけでなく、データを保護する方法を知っている必要があります。
1. SQL インジェクション
スパイシーなストリップのパックなら、間違いなくこれを目にするでしょう。 SQL インジェクションは Web サイトに対する最大の脅威の 1 つです。データベースが他人の SQL インジェクションによって攻撃された場合、他人がデータベースをダンプする可能性があり、より深刻な結果を招く可能性があります。
データベースから動的データを取得するには、Web サイトで次のような SQL ステートメントを実行する必要があります。
<?php $username = $_GET['username']; $query = "SELECT * FROM users WHERE username = '$username'";
攻撃者は、GET および POST を通じて送信されるクエリ (または UA などの他のクエリ) を制御します。 。通常、「peter」という名前のユーザーに対してクエリを実行する SQL ステートメントは次のとおりです:
SELECT * FROM users WHERE username = 'peter'
ただし、攻撃者は特定のユーザー名パラメーターを送信します。例: 'OR '1'='1
これにより、SQL ステートメントは次のようになります:
SELECT * FROM users WHERE username = 'peter' OR '1' = '1'
この方法で、パスワードを必要とせずにユーザー テーブル全体のデータをエクスポートできます。
では、このような事故を防ぐにはどうすればよいでしょうか?主流のソリューションは 2 つあります。ユーザーが入力したデータをエスケープするか、カプセル化されたステートメントを使用します。エスケープする方法は、ユーザーが送信したデータをフィルタリングし、有害なタグを削除する関数をカプセル化することです。ただし、この方法はどこでも忘れやすいため、お勧めしません。
次に、PDO を使用してカプセル化されたステートメントを実行する方法を紹介します (mysqi にも同じことが当てはまります):
$username = $_GET['username']; $query = $pdo->prepare('SELECT * FROM users WHERE username = :username'); $query->execute(['username' => $username]); $data = $query->fetch();
動的データの各部分にはプレフィックスが付きます:。次に、すべての引数を配列として実行関数に渡し、PDO が不正なデータをエスケープしたように見せます。
ほとんどすべてのデータベース ドライバーはカプセル化されたステートメントをサポートしているため、カプセル化されたステートメントを使用しない理由はありません。それらを使用することを習慣にしておけば、後で忘れることがなくなります。
SQL クエリを動的に構築する際のセキュリティ問題の処理については、phpdelusions の記事を参照することもできます。リンク:
https://phpdelusions.net/pdo/sql_injection_example
2. XSS
XSS とも呼ばれますCSS (Cross Site Script)、クロスサイト スクリプティング攻撃。悪意のある攻撃者が Web ページに悪意のある HTML コードを挿入することを指し、ユーザーがそのページを閲覧すると、Web に埋め込まれた HTML コードが実行され、ユーザーに対する悪意のある攻撃という特別な目的が達成されます。
以下は検索ページを例にしています:
<body> <?php $searchQuery = $_GET['q']; /* some search magic here */ ?> <h1>You searched for: <?php echo $searchQuery; ?></h1> <p>We found: Absolutely nothing because this is a demo</p> </body>
ユーザーのコンテンツをフィルタリングせずに直接出力するため、不正なユーザーは URL をつなぎ合わせることができます:
search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E
PHPレンダリングされたコンテンツは次のとおりです。JavaScript コードが直接実行されることがわかります:
<body> <h1>You searched for: <script>alert(1);</script></h1> <p>We found: Absolutely nothing because this is a demo</p> </body>
Q: JS コードが実行されることについて何が重要ですか?
Javascript は次のことができます:
# ユーザーのブラウザの Cookie を盗む;
# ブラウザのパスワード記憶機能を通じてサイトのログインを取得する; アカウント番号とパスワードを取得する;
● ユーザーの機密情報を盗む;
#● サイト上でユーザーが実行できることはすべて、JS 権限実行権限を使用して実行できます。つまり、ユーザー A が任意のユーザーになりすますことができるとします。● Web ページに悪意のあるコードを埋め込む;...Q: この問題を防ぐにはどうすればよいですか? ?
良いニュースは、より高度なブラウザには基本的な XSS 防止機能が備わっていることです。ただし、これに依存しないでください。 正しいアプローチは、ユーザーからの入力を断固として信用せず、入力内のすべての特殊文字をフィルターで除外することです。これにより、ほとんどの XSS 攻撃が排除されます:<?php $searchQuery = htmlentities($searchQuery, ENT_QUOTES);
<body> <a href="<?php echo $homepageUrl; ?>">Visit Users homepage</a> </body>
#" onclick="alert(1)
Visit Users homepage
https://phpdelusions.net/pdo/sql_injection_example を参照してください。
另外设置 Cookie 时,如果无需 JS 读取的话,请必须设置为 "HTTP ONLY"。这个设置可以令 JavaScript 无法读取 PHP 端种的 Cookie。
3. XSRF/CSRF
CSRF 是跨站请求伪造的缩写,它是攻击者通过一些技术手段欺骗用户去访问曾经认证过的网站并运行一些操作。
虽然此处展示的例子是 GET 请求,但只是相较于 POST 更容易理解,并非防护手段,两者都不是私密的 Cookies 或者多步表单。
假如你有一个允许用户删除账户的页面,如下所示:
<?php //delete-account.php $confirm = $_GET['confirm']; if($confirm === 'yes') { //goodbye }
攻击者可以在他的站点上构建一个触发这个 URL 的表单(同样适用于 POST 的表单),或者将 URL 加载为图片诱惑用户点击:
<img src="https://example.com/delete-account.php?confirm=yes" / alt="10 の一般的な PHP セキュリティ問題 (例を挙げて説明)" >
用户一旦触发,就会执行删除账户的指令,眨眼你的账户就消失了。
防御这样的攻击比防御 XSS 与 SQL 注入更复杂一些。
最常用的防御方法是生成一个 CSRF 令牌加密安全字符串,一般称其为 Token,并将 Token 存储于 Cookie 或者 Session 中。
每次你在网页构造表单时,将 Token 令牌放在表单中的隐藏字段,表单请求服务器以后会根据用户的 Cookie 或者 Session 里的 Token 令牌比对,校验成功才给予通过。
由于攻击者无法知道 Token 令牌的内容(每个表单的 Token 令牌都是随机的),因此无法冒充用户。
<?php /* 你嵌入表单的页面 */ ?> <form action="/delete-account.php" method="post"> <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf']; ?>"> <input type="hidden" name="confirm" value="yes" /> <input type="submit" value="Delete my account" /> </form> ## <?php //delete-account.php $confirm = $_POST['confirm']; $csrf = $_POST['csrf']; $knownGoodToken = $_SESSION['csrf']; if($csrf !== $knownGoodToken) { die('Invalid request'); } if($confirm === 'yes') { //goodbye }
请注意,这是个非常简单的示例,你可以加入更多的代码。如果你使用的是像 Symfony 这样的 PHP 框架,那么自带了 CSRF 令牌的功能。
你还可以查看关于 OWASP 更详细的问题和更多防御机制的文章:
https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.md
4. LFI
LFI (本地文件包含) 是一个用户未经验证从磁盘读取文件的漏洞。
我经常遇到编程不规范的路由代码示例,它们不验证过滤用户的输入。我们用以下文件为例,将它要渲染的模板文件用 GET 请求加载。
<body> <?php $page = $_GET['page']; if(!$page) { $page = 'main.php'; } include($page); ?> </body>
由于 Include 可以加载任何文件,不仅仅是 PHP,攻击者可以将系统上的任何文件作为包含目标传递。
index.php?page=../../etc/passwd
这将导致 /etc/passwd 文件被读取并展示在浏览器上。
要防御此类攻击,你必须仔细考虑允许用户输入的类型,并删除可能有害的字符,如输入字符中的 “.” “/” “\”。
如果你真的想使用像这样的路由系统(我不建议以任何方式),你可以自动附加 PHP 扩展,删除任何非 [a-zA-Z0-9-_] 的字符,并指定从专用的模板文件夹中加载,以免被包含任何非模板文件。
我在不同的开发文档中,多次看到造成此类漏洞的 PHP 代码。从一开始就要有清晰的设计思路,允许所需要包含的文件类型,并删除掉多余的内容。你还可以构造要读取文件的绝对路径,并验证文件是否存在来作为保护,而不是任何位置都给予读取。
5. 不充分的密码哈希
大部分的 Web 应用需要保存用户的认证信息。如果密码哈希做的足够好,在你的网站被攻破时,即可保护用户的密码不被非法读取。
首先,最不应该做的事情,就是把用户密码明文储存起来。大部分的用户会在多个网站上使用同一个密码,这是不可改变的事实。当你的网站被攻破,意味着用户的其他网站的账号也被攻破了。
其次,你不应该使用简单的哈希算法,事实上所有没有专门为密码哈希优化的算法都不应使用。哈希算法如 MD5 或者 SHA 设计初衷就是执行起来非常快。这不是你需要的,密码哈希的终极目标就是让黑客花费无穷尽的时间和精力都无法破解出来密码。
另外一个比较重要的点是你应该为密码哈希加盐(Salt),加盐处理避免了两个同样的密码会产生同样哈希的问题。
以下使用 MD5 来做例子,所以请千万不要使用 MD5 来哈希你的密码, MD5 是不安全的。
假如我们的用户 user1 和 user315 都有相同的密码 ilovecats123,这个密码虽然看起来是强密码,有字母有数字,但是在数据库里,两个用户的密码哈希数据将会是相同的:5e2b4d823db9d044ecd5e084b6d33ea5 。
如果一个如果黑客拿下了你的网站,获取到了这些哈希数据,他将不需要去暴力破解用户 user315 的密码。我们要尽量让他花大精力来破解你的密码,所以我们对数据进行加盐处理:
<?php //warning: !!这是一个很不安全的密码哈希例子,请不要使用!! $password = 'cat123'; $salt = random_bytes(20); $hash = md5($password . $salt);
最后在保存你的唯一密码哈希数据时,请不要忘记连 $salt 也已经保存,否则你将无法验证用户。
在当下,最好的密码哈希选项是 bcrypt,这是专门为哈希密码而设计的哈希算法,同时这套哈希算法里还允许你配置一些参数来加大破解的难度。
新版的 PHP 中也自带了安全的密码哈希函数 password_hash
,此函数已经包含了加盐处理。对应的密码验证函数为 password_verify
用来检测密码是否正确。password_verify
还可有效防止 时序攻击.
以下是使用的例子:
<?php //user signup $password = $_POST['password']; $hashedPassword = password_hash($password, PASSWORD_DEFAULT); //login $password = $_POST['password']; $hash = '1234'; //load this value from your db if(password_verify($password, $hash)) { echo 'Password is valid!'; } else { echo 'Invalid password.'; }
需要澄清的一点是:密码哈希并不是密码加密。哈希(Hash)是将目标文本转换成具有相同长度的、不可逆的杂凑字符串(或叫做消息摘要),而加密(Encrypt)是将目标文本转换成具有不同长度的、可逆的密文。显然他们之间最大的区别是可逆性,在储存密码时,我们要的就是哈希这种不可逆的属性。
6. 中间人攻击
MITM (中间人) 攻击不是针对服务器直接攻击,而是针对用户进行,攻击者作为中间人欺骗服务器他是用户,欺骗用户他是服务器,从而来拦截用户与网站的流量,并从中注入恶意内容或者读取私密信息,通常发生在公共 WiFi 网络中,也有可能发生在其他流量通过的地方,例如 ISP 运营商。
对此的唯一防御是使用 HTTPS,使用 HTTPS 可以将你的连接加密,并且无法读取或者篡改流量。你可以从 Let's Encrypt 获取免费的 SSL 证书,或从其他供应商处购买,这里不详细介绍如何正确配置 WEB 服务器,因为这与应用程序安全性无关,且在很大程度上取决于你的设置。
你还可以采取一些措施使 HTTPS 更安全,在 WEB 服务器配置加上 Strict-Transport-Security 标示头,此头部信息告诉浏览器,你的网站始终通过 HTTPS 访问,如果未通过 HTTPS 将返回错误报告提示浏览器不应显示该页面。
然而,这里有个明显的问题,如果浏览器之前从未访问过你的网站,则无法知道你使用此标示头,这时候就需要用到 Hstspreload。
可以在此注册你的网站:
https://hstspreload.org/
你在此处提交的所有网站都将被标记为仅 HTTPS,并硬编码到 Google Chrome、FireFox、Opera、Safari、IE11 和 Edge 的源代码中。
你还可以在 DNS 配置中添加 Certification Authority Authorization (CAA) record
,可以仅允许一个证书颁发机构(例如: Let's encrypt)发布你的域名证书,这进一步提高了用户的安全性。
7. 命令注入
这可能是服务器遇到的最严重的攻击,命令注入的目标是欺骗服务器执行任意 Shell 命令
你如果使用 shell_exec 或是 exec 函数。让我们做一个小例子,允许用户简单的从服务器 Ping 不同的主机。
<?php $targetIp = $_GET['ip']; $output = shell_exec("ping -c 5 $targetIp");
输出将包括对目标主机 Ping 5 次。除非采用 sh 命令执行 Shell 脚本,否则攻击者可以执行想要的任何操作。
ping.php?ip=8.8.8.8;ls -l /etc
Shell 将执行 Ping 和由攻击者拼接的第二个命令,这显然是非常危险的。
感谢 PHP 提供了一个函数来转义 Shell 参数。
escapeshellarg 转义用户的输入并将其封装成单引号。
<?php $targetIp = escapeshellarg($_GET['ip']); $output = shell_exec("ping -c 5 $targetIp");
现在你的命令应该是相当安全的,就个人而言,我仍然避免使用 PHP 调用外部命令,但这完全取决于你自己的喜好。
另外,我建议进一步验证用户输入是否符合你期望的形式。
8. XXE
XXE (XML 外部实体) 是一种应用程序使用配置不正确的 XML 解析器解析外部 XML 时,导致的本地文件包含攻击,甚至可以远程代码执行。
XML 有一个鲜为人知的特性,它允许文档作者将远程和本地文件作为实体包含在其 XML 文件中。
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [ <!ELEMENT foo ANY > <!ENTITY passwd SYSTEM "file:///etc/passwd" >]> <foo>&passwd;</foo>
就像这样, /etc/passwd 文件内容被转储到 XML 文件中。
如果你使用 libxml 可以调用 libxml_disable_entity_loader
来保护自己免受此类攻击。使用前请仔细检查 XML 库的默认配置,以确保配置成功。
9. 在生产环境中不正确的错误报告暴露敏感数据
如果你不小心,可能会在生产环境中因为不正确的错误报告泄露了敏感信息,例如:文件夹结构、数据库结构、连接信息与用户信息。
你是不希望用户看到这个的吧?
通常、設定方法は使用するフレームワークや CMS によって異なります。多くの場合、フレームワークには、サイトを何らかの運用環境に変更できるようにする設定が含まれています。これにより、ユーザーに表示されるすべてのエラー メッセージがログ ファイルにリダイレクトされ、説明のない 500 エラーがユーザーに表示されると同時に、エラー コードに基づいて確認できるようになります。
ただし、PHP 環境に応じて設定する必要があります: error_reporting
および display_errors
.
10. ログイン制限
ログインなどの機密性の高いフォームには、ブルート フォース攻撃を防ぐために厳格なレート制限を設ける必要があります。過去数分間の各ユーザーのログイン試行失敗回数を保存し、その割合が定義したしきい値を超えた場合は、クールダウン期間が終了するまでそれ以降のログイン試行を拒否します。ユーザーはログイン試行の失敗を電子メールで通知されるため、自分のアカウントが標的にされていることを知ることができます。
その他の追加事項
● ユーザーから渡されたオブジェクト ID を信頼せず、要求されたオブジェクトへのユーザーのアクセスを常に確認してください
● サーバー 使用するライブラリを常に最新の状態に保ちます
#● セキュリティ関連のブログを購読して最新のソリューションについて学びます#● ユーザー パスワードをログに保存しないでください
● やってはいけないこと コード ベース全体は WEB ルート ディレクトリに保存されます。
#● コード ベース全体を漏洩したくない場合を除き、WEB ルート ディレクトリに Git リポジトリを作成しないでください。#● 常にユーザー入力は安全ではないと想定します。
# URL やクローラをランダムにスキャンするツールなど、疑わしい動作をする IP アドレスの表示を禁止するようにシステムを設定します。
## ● サードパーティを過度に信頼しないでください。安全のためのパーティ コードを使用しないでください。#●● Composer は Github から直接コードを取得します。
# サイトをサードパーティによってクロスドメイン iframe にしたくない場合は、 anti-iframe ヘッダー
##● あいまいさは安全ではありません#● 実務経験が不足しているオペレーターまたは共同開発者の場合は、できるだけ頻繁にコードを確認してください
● セキュリティ機能がどのように機能するのか、またはなぜインストールされているのかがわからない場合は、無視せずに、知っている人に尋ねてください。
##● 独自の暗号化を決して作成しないでください。悪いアプローチ#● 十分なエントロピーがない場合は、疑似乱数生成を正しくシードして破棄してください
● インターネットが安全ではなく、情報が盗まれる可能性がある場合は、次の準備をしてください。この状況に対処し、インシデント対応計画を作成します。
#● WEB ルート ディレクトリのリスト表示を無効にします。多くの WEB サーバー構成ではデフォルトでこれが行われます。 ディレクトリの内容をリストします。これはデータ漏洩につながる可能性があります。●クライアント側の検証だけでは十分ではありません。PHP 内のすべてのコンテンツを再度検証する必要があります。#● ユーザー コンテンツの逆シリアル化は絶対に避けてください。リモートでコードが実行される可能性があります。この問題の詳細については、この記事を参照してください。 :
https://paragonie.com/blog/2016/04/securely-implementing-de-serialization-in -phpヒント
私はセキュリティの専門家ではないため、すべてを詳細に行うことはできない可能性があります。安全なソフトウェアを作成するのは骨の折れるプロセスですが、いくつかの基本的なルールに従うことで、ある程度安全なアプリケーションを作成することは可能です。実際、多くのフレームワークがこの点で多くの作業を行うのに役立ちました。 セキュリティ問題は、問題が発生する前の開発段階で追跡できる構文エラーとは異なります。したがって、コードを記述するときは、セキュリティ リスクの回避を常に意識する必要があります。ビジネス上の必要性からのプレッシャーにより、一時的にセキュリティ上の注意事項を無視せざるを得なくなる場合は、そうすることによる潜在的なリスクを事前に全員に知らせる必要があると思います。この記事が役に立った場合は、お友達と共有して、一緒に安全な Web サイトを構築しましょう。
以上が10 の一般的な PHP セキュリティ問題 (例を挙げて説明)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。