Home  >  Article  >  Backend Development  >  10 common security issues in PHP (explanation with examples)

10 common security issues in PHP (explanation with examples)

Guanhui
Guanhuiforward
2020-06-06 09:08:482636browse

10 common security issues in PHP (explanation with examples)

Compared with other languages, PHP has greater advantages in building web sites. Even a novice can easily build a website. But this advantage can also easily bring about some negative effects, because many PHP tutorials do not involve security knowledge.

This post is divided into several parts, each part will cover different security threats and countermeasures. However, this does not mean that after you do these points, you will definitely avoid any problems with your website. If you want to improve the security of your website, you should continue to study how to improve the security of your website by reading books or articles

For demonstration purposes, the code may not be perfect. In the daily development process, a lot of code is included in frameworks and various libraries. As a backend developer, you must not only be proficient in basic CURD, but also know how to protect your data.

1. SQL Injection

I bet you a pack of spicy strips, you will definitely see this. SQL injection is one of the biggest threats to your website. If your database is attacked by someone else's SQL injection, someone else can dump your database, which may have more serious consequences.

To obtain dynamic data from the database, the website must execute SQL statements, for example:

<?php
$username = $_GET[&#39;username&#39;];
$query = "SELECT * FROM users WHERE username = &#39;$username&#39;";

The attacker controls the queries sent through GET and POST (or some other queries such as UA) . Normally, the SQL statement you want to query for a user named "peter" is as follows:

SELECT * FROM users WHERE username = &#39;peter&#39;

However, the attacker sends a specific username parameter, for example: 'OR '1'='1

This will cause the SQL statement to become like this:

SELECT * FROM users WHERE username = &#39;peter&#39; OR '1' = '1'

In this way, he can export the data of your entire user table without requiring a password.

So, how do we prevent such accidents from happening? There are two mainstream solutions. Escape user-entered data or use encapsulated statements. The method of escaping is to encapsulate a function to filter the data submitted by the user and remove harmful tags. However, I don't recommend this method because it's easier to forget to do it everywhere.

Next, I will introduce how to use PDO to execute encapsulated statements (the same is true for mysqi):

$username = $_GET[&#39;username&#39;];
$query = $pdo->prepare(&#39;SELECT * FROM users WHERE username = :username&#39;);
$query->execute([&#39;username&#39; => $username]);
$data = $query->fetch();

Each part of dynamic data is prefixed with:. Then pass all arguments to the execution function as an array, making it look like PDO escaped the bad data for you.

Almost all database drivers support encapsulated statements, there is no reason not to use them! Make it a habit to use them and you won’t forget them later.

2. XSS

XSS is also called CSS (Cross Site Script), a cross-site scripting attack. It refers to a malicious attacker inserting malicious html code into a Web page. When a user browses the page, the html code embedded in the Web will be executed, thereby achieving the special purpose of maliciously attacking the user.

The following takes a search page as an example:

<body>
<?php
$searchQuery = $_GET[&#39;q&#39;];
/* some search magic here */
?>
<h1>You searched for: <?php echo $searchQuery; ?></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>

Because we print out the user's content directly without any filtering, illegal users can splice URLs:

search.php?q=%3Cscript%3Ealert(1)%3B%3C%2Fscript%3E

PHP The rendered content is as follows. You can see that the Javascript code will be executed directly:

<body>
<h1>You searched for: <script>alert(1);</script></h1>
<p>We found: Absolutely nothing because this is a demo</p>
</body>

Q: What’s the big deal about JS code being executed?

Javascript can:

steal the cookies in your user's browser;

obtain your site login account and password through the browser's remember password function;

Steal users’ confidential information;

Everything your users can do on the site can be done with JS permission execution permissions, which means that user A can impersonate anyone. User;

embeds malicious code in your webpage;

...

Q: How to prevent this problem?

The good news is that more advanced browsers now have some basic XSS prevention functions, but please do not rely on this.

The correct approach is to resolutely not trust any input from the user and filter out all special characters in the input. This will eliminate most XSS attacks:

<?php
$searchQuery = htmlentities($searchQuery, ENT_QUOTES);

Or you can use the template engine Twig. General template engines will add htmlentities to the output by default.

If you keep the user's input content, pay special attention when outputting it. In the following example, we allow users to fill in their own blog links:

<body>
  <a href="<?php echo $homepageUrl; ?>">Visit Users homepage</a>
</body>

The above code may be the first There is no problem at all, but suppose the user fills in the following content:

#" onclick="alert(1)

will be rendered as:


  Visit Users homepage

Never, never trust the data entered by the user, or, always assume that the user's The content is offensive, the attitude is correct, and every user input and output is carefully handled.

In addition, when setting cookies, if JS is not required to read, please set it to "HTTP ONLY". This setting prevents JavaScript from reading cookies from the PHP side.

3. Run some operations.

虽然此处展示的例子是 GET 请求,但只是相较于 POST 更容易理解,并非防护手段,两者都不是私密的 Cookies 或者多步表单。

假如你有一个允许用户删除账户的页面,如下所示:

<?php
//delete-account.php
$confirm = $_GET[&#39;confirm&#39;];
if($confirm === &#39;yes&#39;) {
  //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[&#39;csrf&#39;]; ?>">
  <input type="hidden" name="confirm" value="yes" />
  <input type="submit" value="Delete my account" />
</form>

## 

<?php
//delete-account.php
$confirm = $_POST[&#39;confirm&#39;];
$csrf = $_POST[&#39;csrf&#39;];
$knownGoodToken = $_SESSION[&#39;csrf&#39;];
if($csrf !== $knownGoodToken) {
  die(&#39;Invalid request&#39;);
}
if($confirm === &#39;yes&#39;) {
  //goodbye
}

请注意,这是个非常简单的示例,你可以加入更多的代码。如果你使用的是像 Symfony 这样的 PHP 框架,那么自带了 CSRF 令牌的功能。

你还可以查看关于 OWASP 更详细的问题和更多防御机制的文章: https://github.com/OWASP/CheatS....

4. LFI

LFI (本地文件包含) 是一个用户未经验证从磁盘读取文件的漏洞。

我经常遇到编程不规范的路由代码示例,它们不验证过滤用户的输入。我们用以下文件为例,将它要渲染的模板文件用 GET 请求加载。

<body>
<?php
  $page = $_GET[&#39;page&#39;];
  if(!$page) {
    $page = &#39;main.php&#39;;
  }
  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 = &#39;cat123&#39;;
$salt = random_bytes(20);
$hash = md5($password . $salt);

最后在保存你的唯一密码哈希数据时,请不要忘记连 $salt 也已经保存,否则你将无法验证用户。

在当下,最好的密码哈希选项是 bcrypt,这是专门为哈希密码而设计的哈希算法,同时这套哈希算法里还允许你配置一些参数来加大破解的难度。

新版的 PHP 中也自带了安全的密码哈希函数 password_hash ,此函数已经包含了加盐处理。对应的密码验证函数为 password_verify 用来检测密码是否正确。password_verify 还可有效防止 时序攻击.

以下是使用的例子:

<?php
//user signup
$password = $_POST[&#39;password&#39;];
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);
//login
$password = $_POST[&#39;password&#39;];
$hash = &#39;1234&#39;; //load this value from your db
if(password_verify($password, $hash)) {
  echo &#39;Password is valid!&#39;;
} else {
  echo &#39;Invalid password.&#39;;
}

需要澄清的一点是:密码哈希并不是密码加密。哈希(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[&#39;ip&#39;];
$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[&#39;ip&#39;]);
$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. 登录限制

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

一些其他补充

  • Don't trust the object ID passed to you from the user, always verify the user's access to the requested object

  • The server and the libraries used are always up to date

  • Subscribe to follow security-related blogs to learn about the latest solutions

  • Never save user passwords in logs

  • Don’t store the entire code base in the WEB root directory

  • Never create a Git repository in the WEB root directory unless you want to leak the entire code base

  • Always assume that user input is unsafe

  • Set the system to prohibit the display of IP addresses with suspicious behavior, such as tools that randomly scan URLs and crawlers

  • Don’t overly trust that third-party code is safe

  • Do not use Composer to get the code directly from Github

  • If you do not want your site to be cross-domain iframed by a third party, please set the anti-iframe header

  • Ambiguous is unsafe

  • If you are lacking Practical experience operators or co-developers, please make sure to check the code as often as possible

  • When you don't understand how a security feature is supposed to work, or why it is installed, ask someone who knows, Don't ignore it

  • Never write your own encryption, it's probably a bad approach

  • If you don't have enough entropy, seed it correctly Your pseudo-random number generation and discard

  • If the Internet is not secure and there is a possibility of information being stolen, prepare for this scenario and have an incident response plan

  • Disable the WEB root directory list display. Many WEB server configurations will list directory contents by default, which may lead to data leakage

  • Client verification is not enough , need to verify everything in PHP again

Tips

I'm not a security expert, I'm afraid I can't do everything in detail. Although writing secure software can be a painful process, it is possible to write reasonably secure applications by following some basic rules. In fact, many frameworks have helped us do a lot of work in this regard.

Security issues are not like syntax errors that can be tracked during the development phase before the problem occurs. Therefore, when writing code, you should always be aware of avoiding security risks. If you are forced to temporarily ignore some security precautions due to pressure from business needs, I think you need to inform everyone in advance of the potential risks of doing so.

If you have benefited from this article, please share it with your friends and let us build a safe website together.

Recommended tutorial: "PHP Tutorial"

The above is the detailed content of 10 common security issues in PHP (explanation with examples). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:learnku.com. If there is any infringement, please contact admin@php.cn delete