PHP 安全问题入门:10 个常见安全问题 + 实例讲解

file

文章转自:learnku.com/php/t/24930
更多文章:learnku.com/laravel/c/t…php

相对于其余几种语言来讲, PHP 在 web 建站方面有更大的优点,即便是新手,也能很容易搭建一个网站出来。但这种优点也容易带来一些负面影响,由于不少的 PHP 教程没有涉及到安全方面的知识。html

此帖子分为几部分,每部分会涵盖不一样的安全威胁和应对策略。可是,这并非说你作到这几点之后,就必定能避免你的网站出现任何问题。若是你想提升你的网站安全性的话,你应该继续经过阅读书籍或者文章,来研究如何提升你的网站安全性html5

出于演示须要,代码可能不是很完美。平常开发过程当中,不少代码都包含在了框架跟各类库里面。做为一个后台开发,你不只要熟练基本的CURD,更要知道如何保护你的数据。laravel

1. SQL 注入

我赌一包辣条,你确定会看到这里。 SQL 注入是对您网站最大的威胁之一,若是您的数据库受到别人的 SQL 注入的攻击的话,别人能够转出你的数据库,也许还会产生更严重的后果。git

网站要从数据库中获取动态数据,就必须执行 SQL 语句,举例以下:github

<?php

$username = $_GET['username'];
$query = "SELECT * FROM users WHERE username = '$username'";

复制代码

攻击者控制经过 GET 和 POST 发送的查询(或者例如 UA 的一些其余查询)。通常状况下,你但愿查询户名为「 peter 」的用户产生的 SQL 语句以下:web

SELECT * FROM users WHERE username = 'peter'
复制代码

可是,攻击者发送了特定的用户名参数,例如:' OR '1'='1算法

这就会致使 SQL 语句变成这样:sql

SELECT * FROM users WHERE username = 'peter' OR '1' = '1'
复制代码

这样,他就能在不须要密码的状况下导出你的整个用户表的数据了。shell

那么,咱们如何防止这类事故的发生呢?主流的解决方法有两种。转义用户输入的数据或者使用封装好的语句。转义的方法是封装好一个函数,用来对用户提交的数据进行过滤,去掉有害的标签。可是,我不太推荐使用这个方法,由于比较容易忘记在每一个地方都作此处理。

下面,我来介绍如何使用 PDO 执行封装好的语句( mysqi 也同样):

$username = $_GET['username'];
$query = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$query->execute(['username' => $username]);
$data = $query->fetch();
复制代码

动态数据的每一个部分都以:作前缀。而后将全部参数做为数组传递给执行函数,看起来就像 PDO 为你转义了有害数据同样。

几乎全部的数据库驱动程序都支持封装好的语句,没有理由不使用它们!养成使用他们的习惯,之后就不会忘记了。

你也能够参考 phpdelusions 中的一篇关于动态构建 SQL 查询时处理安全问题的文章。连接:  phpdelusions.net/pdo/sql_inj…

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 标签,或者标头信息,更多详情请见: www.html5rocks.com/en/tutorial…

另外种 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 更详细的问题和更多防护机制的文章: github.com/OWASP/Cheat…

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 是不安全的。

假如咱们的用户 user1user315 都有相同的密码 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。

能够在此注册你的网站: 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. 在生产环境中不正确的错误报告暴露敏感数据

若是你不当心,可能会在生产环境中由于不正确的错误报告泄露了敏感信息,例如:文件夹结构、数据库结构、链接信息与用户信息。

file

你是不但愿用户看到这个的吧?

通常根据你使用的框架或者 CMS ,配置方法会有不一样的变化。一般框架具备容许你将站点更改成某种生产环境的设置。这样会将全部用户可见的错误消息重定向到日志文件中,并向用户显示非描述性的 500 错误,同时容许你根据错误代码检查。

可是你应该根据你的 PHP 环境设置: error_reporting 与 display_errors.

10. 登陆限制

像登陆这样的敏感表单应该有一个严格的速率限制,以防止暴力攻击。保存每一个用户在过去几分钟内失败的登陆尝试次数,若是该速率超过你定义的阈值,则拒绝进一步登陆尝试,直到冷却期结束。还可经过电子邮件通知用户登陆失败,以便他们知道本身的帐户被成为目标。

一些其余补充

  • 不要信任从用户传递给你的对象 ID ,始终验证用户对请求对象的访问权限

  • 服务器与使用的库时刻保持最新

  • 订阅关注安全相关的博客,了解最新的解决方案

  • 从不在日志中保存用户的密码

  • 不要将整个代码库存储在 WEB 根目录中

  • 永远不要在 WEB 根目录建立 Git 存储库,除非你但愿泄露整个代码库

  • 始终假设用户的输入是不安全的

  • 设置系统禁止可疑行为的 IP 显示,例如:工具对 URL 随机扫描、爬虫

  • 不要过度信任第三方代码是安全的

  • 不要用 Composer 直接从 Github 获取代码

  • 若是不但愿站点被第三方跨域 iframe,请设置反 iframe 标示头

  • 含糊是不安全的

  • 若是你是缺少实践经验的运营商或合做开发人员,请确保尽量时常检查代码

  • 当你不了解安全功能应该如何工做,或者为何会安装,请询问知道的人,不要忽视它

  • 永远不要本身写加密方式,这多是个坏的方法

  • 若是你没有足够的熵,请正确播种你的伪随机数生成并舍弃

  • 若是在互联网上不安全,并有可能被窃取信息,请为这种状况作好准备并制定事件响应计划

  • 禁用 WEB 根目录列表显示,不少 WEB 服务器配置默认都会列出目录内容,这可能致使数据泄露

  • 客户端验证是不够的,须要再次验证 PHP 中的全部内容

  • 不惜一切代价避免反序列化用户内容,这可能致使远程代码执行,有关此问题的详细信息,请参阅此文章: paragonie.com/blog/2016/0…

小贴士

我不是一个安全专家,恐没法作到事无巨细。尽管编写安全软件是一个很是痛苦的过程,但仍是能够经过遵循一些基本规则,编写合理安全的应用程序。其实,不少框架在这方面也帮咱们作了不少工做。

在问题发生以前,安全性问题并不像语法错误等能够在开发阶段追踪到。所以,在编写代码的过程当中,应该时刻有规避安全风险的意识。若是你迫于业务需求的压力而不得不暂时忽略一些安全防范的工做,我想你有必要事先告知你们这样作的潜在风险。

若是你从这篇文章有所收益,也请把它分享给你的朋友们把,让咱们共建安全网站。

文章转自:learnku.com/php/t/24930
更多文章:learnku.com/laravel/c/t…

相关文章
相关标签/搜索