相对付其他几种措辞来说, PHP 在 web 建站方面有更大的上风,纵然是新手,也能很随意马虎搭建一个网站出来。但这种上风也随意马虎带来一些负面影响,由于很多的 PHP 教程没有涉及到安全方面的知识。
此帖子分为几部分,每部分会涵盖不同的安全威胁和应对策略。但是,这并不是说你做到这几点往后,就一定能避免你的网站涌现任何问题。如果你想提高你的网站安全性的话,你该当连续通过阅读书本或者文章,来研究如何提高你的网站安全性
出于演示须要,代码可能不是很完美。日常开拓过程中,很多代码都包含在了框架跟各种库里面。作为一个后台开拓,你不仅要闇练基本的CURD,更要知道如何保护你的数据。
1. SQL 注入
我赌一包辣条,你肯定会看到这里。 SQL 注入是对您网站最大的威胁之一,如果您的数据库受到别人的 SQL 注入的攻击的话,别人可以转出你的数据库,大概还会产生更严重的后果。
网站要从数据库中获取动态数据,就必须实行 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'
这样,他就能在不须要密码的情形下导出你的全体用户表的数据了。
那么,我们如何防止这类事件的发生呢?主流的办理方法有两种。转义用户输入的数据或者利用封装好的语句。转义的方法是封装好一个函数,用来对用户提交的数据进行过滤,去掉有害的标签。但是,我不太推举利用这个方法,由于比较随意马虎忘却在每个地方都做此处理。
下面,我来先容如何利用 PDO 实行封装好的语句( mysqi 也一样):
$username = $_GET['username']; $query = $pdo->prepare('SELECT FROM users WHERE username = :username'); $query->execute(['username' => $username]); $data = $query->fetch();
动态数据的每个部分都以:做前缀。然后将所有参数作为数组通报给实行函数,看起来就像 PDO 为你转义了有害数据一样。
险些所有的数据库驱动程序都支持封装好的语句,没有情由不该用它们!
养成利用他们的习气,往后就不会忘却了。
你也可以参考 phpdelusions 中的一篇关于动态构建 SQL 查询时处理安全问题的文章。链接: https://phpdelusions.net/pdo/... 。
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>
问:JS 代码被实行有什么大不了的?
Javascript 可以:
偷走你用户浏览器里的 Cookie; 通过浏览器的记住密码功能获取到你的站点登录账号和密码; 盗取用户的机密信息; 你的用户在站点上能做到的事情,有了 JS 权限实行权限就都能做,也便是说 A 用户可以仿照成为任何用户; 在你的网页中嵌入恶意代码; ...问:如何戒备此问题呢?
好是比较前辈的浏览器现在已经具备了一些根本的 XSS 戒备功能,不过请不要依赖与此。
精确的做法是武断不要相信用户的任何输入,并过滤掉输入中的所有分外字符。这样就能消灭绝大部分的 XSS 攻击:
<?php $searchQuery = htmlentities($searchQuery, ENT_QUOTES);
或者你可以利用模板引擎 Twig ,一样平常的模板引擎都会默认为输出加上 htmlentities 戒备。
如果你保持了用户的输入内容,在输出时也要特殊把稳,在以下的例子中,我们许可用户填写自己的博客链接:
<body> <a href=\公众<?php echo $homepageUrl; ?>\公众>Visit Users homepage</a> </body>
以上代码可能第一眼看不出来有问题,但是假设用户填入以下内容:
#\"大众 onclick=\"大众alert(1)
会被渲染为:
<body> <a href=\公众#\公众 onclick=\公众alert(1)\公众>Visit Users homepage</a> </body>
永久永久不要相信用户输入的数据,或者,永久都假设用户的内容是有攻击性的,态度端正了,然后小心地处理好每一次的用户输入和输出。
另一个掌握 XSS 攻击的方法是供应一个 CSP Meta 标签,或者标头信息,更多详情请见:https://www.html5rocks.com/en...
其余种 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\"大众 />
用户一旦触发,就会实行删除账户的指令,眨眼你的账户就消逝了。
防御这样的攻击比防御 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/CheatS....
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. 在生产环境中禁绝确的缺点报告暴露敏感数据
[](https://secure.php.net/manual...,可能会在生产环境中由于禁绝确的缺点报告透露了敏感信息,例如:文件夹构造、数据库构造、连接信息与用户信息。
你是不肯望用户看到这个的吧?
一样平常根据你利用的框架或者 CMS ,配置方法会有不同的变革。常日框架具有许可你将站点变动为某种生产环境的设置。这样会将所有用户可见的缺点重定向到日志文件中,并向用户显示非描述性的 500 缺点,同时许可你根据缺点代码检讨。
但是你该当根据你的 PHP 环境设置: error_reporting 与 display_errors.
10. 登录限定
像登录这样的敏感表单该当有一个严格的速率限定,以防止暴力攻击。保存每个用户在过去几分钟内失落败的登录考试测验次数,如果该速率超过你定义的阈值,则谢绝进一步登录考试测验,直到冷却期结束。还可通过电子邮件关照用户登录失落败,以便他们知道自己的账户被成为目标。
一些其他补充
不要信赖从用户通报给你的工具 ID ,始终验证用户对要求工具的访问权限 做事器与利用的库时候保持最新 订阅关注安全干系的博客,理解最新的办理方案 从不在日志中保存用户的密码 不要将全体代码库存储在 WEB 根目录中 永久不要在 WEB 根目录创建 Git 存储库,除非你希望透露全体代码库 始终假设用户的输入是不屈安的 设置系统禁止可疑行为的 IP 显示,例如:工具对 URL 随机扫描、爬虫 不要过分信赖第三方代码是安全的 不要用 Composer 直接从 Github 获取代码 如果不肯望站点被第三方跨域 iframe,请设置反 iframe 标示头 暗昧是不屈安的 如果你是缺少实践履历的运营商或互助开拓职员,请确保尽可能时常检讨代码 当你不理解安全功能该当如何事情,或者为什么会安装,请讯问知道的人,不要忽略它 永久不要自己写加密办法,这可能是个坏的方法 如果你没有足够的熵,请精确播种你的伪随机数天生并舍弃 如果在互联网上不屈安,并有可能被窃取信息,请为这种情形做好准备并制订事宜相应操持 禁用 WEB 根目录列表显示,很多 WEB 做事器配置默认都会列出目录内容,这可能导致数据透露 客户端验证是不足的,须要再次验证 PHP 中的所有内容 不惜统统代价避免反序列化用户内容,这可能导致远程代码实行,有关此问题的详细信息,请参阅此文章: https://paragonie.com/blog/20...小贴士
我不是一个安全专家,恐无法做到事无年夜小。只管编写安全软件是一个非常痛楚的过程,但还是可以通过遵照一些基本规则,编写合理安全的运用程序。实在,很多框架在这方面也帮我们做了很多事情。
在问题发生之前,安全性问题并不像语法缺点等可以在开拓阶段追踪到。因此,在编写代码的过程中,该当时候有规避安全风险的意识。如果你迫于业务需求的压力而不得不暂时忽略一些安全戒备的事情,我想你有必要事先奉告大家这样做的潜在风险。
如果你从这篇文章有所收益,也请把它分享给你的朋友们把,让我们共建安全网站。
天下数据是海内屈指可数的拥有多处外洋自建机房的新型IDC做事商,被业界公认为“中国IDC行业首选品牌”。
天下数据与环球近120多个国家顶级机房直接互助,供应包括喷鼻香港、美国、韩国、日本、台湾、新加坡、荷兰、法国、英国、德国、埃及、南非、巴西、印度、越南等国家和地区的做事器、云做事器的租用做事.
除供应传统的IDC产品外,天下数据的紧张职责是为大中型企业供应更风雅、安全、知足个性需求的定制化做事器办理方案,特殊是在直销、金融、视频、流媒体、游戏、电子商务、区块链、快消、物联网、大数据等诸多行业,为广大客户办理做事器租用中碰着的各种问题。